WIP
This commit is contained in:
parent
b790a70d3a
commit
2410ef056c
|
|
@ -0,0 +1,9 @@
|
||||||
|
type AuthOauth2ClientToAuthRealmEdge {
|
||||||
|
data: AuthRealm
|
||||||
|
error: AuthOauth2ClientToAuthRealmError
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthOauth2ClientToAuthOauth2ScopesEdge {
|
||||||
|
data: [AuthOauth2Scope]
|
||||||
|
error: AuthOauth2ClientToAuthOauth2ScopesError
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
enum AuthOauth2ClientTypeEnum {
|
||||||
|
CONFIDENTIAL
|
||||||
|
PUBLIC
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
enum AuthOauth2ClientToAuthRealmError {
|
||||||
|
UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AuthOauth2ClientToAuthOauth2ScopesError {
|
||||||
|
UNKNOWN
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
type AuthRealm {
|
||||||
|
urn: ID!
|
||||||
|
name: String
|
||||||
|
createdAt: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthOauth2Client {
|
||||||
|
urn: ID!
|
||||||
|
clientId: String!
|
||||||
|
clientType: AuthOauth2ClientTypeEnum!
|
||||||
|
clientSecret: String
|
||||||
|
|
||||||
|
Realm: AuthOauth2ClientToAuthRealmEdge
|
||||||
|
Scopes: AuthOauth2ClientToAuthOauth2Scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthOauth2Scope {
|
||||||
|
urn: ID!
|
||||||
|
scope: String
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
enum IdentityAuthDeviceTypeEnum {
|
enum IdentityAuthDeviceTypeEnum {
|
||||||
PASSWORD
|
PASSWORD
|
||||||
|
APPLICATION_PASSWORD
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IdentityGroupRoleEnum {
|
||||||
|
SYSTEM_ADMIN
|
||||||
|
REALM_ADMIN
|
||||||
|
STANDARD
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
type IdentityUserOutput {
|
||||||
|
error:
|
||||||
|
data: IdentityUser
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
identityUsers()
|
||||||
|
identityUser(urn: String!): IdentityUserOutput!
|
||||||
|
myUser
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
type IdentityGroup {
|
type IdentityGroup {
|
||||||
urn: ID!
|
urn: ID!
|
||||||
isAdmin: Boolean!
|
isAdmin: Boolean!
|
||||||
|
role: IdentityGroupRoleEnum!
|
||||||
name: String
|
name: String
|
||||||
|
|
||||||
Users: IdentityGroupToIdentityUserEdge!
|
Users: IdentityGroupToIdentityUserEdge!
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,16 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/server": "^4.10.2",
|
"@apollo/server": "^4.10.2",
|
||||||
"@nestjs/apollo": "^12.1.0",
|
"@nestjs/apollo": "^12.1.0",
|
||||||
|
"@nestjs/cache-manager": "^2.2.2",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/graphql": "^12.1.1",
|
"@nestjs/graphql": "^12.1.1",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@prisma/client": "^5.12.1",
|
"@prisma/client": "^5.12.1",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
|
"cache-manager": "^5.5.1",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
"joi": "^17.12.3",
|
"joi": "17.6.4",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
|
|
@ -4315,6 +4317,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/cache-manager": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-+n7rpU1QABeW2WV17Dl1vZCG3vWjJU1MaamWgZvbGxYE9EeCM0lVLfw3z7acgDTNwOy+K68xuQPoIMxD0bhjlA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^9.0.0 || ^10.0.0",
|
||||||
|
"@nestjs/core": "^9.0.0 || ^10.0.0",
|
||||||
|
"cache-manager": "<=5",
|
||||||
|
"rxjs": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/cli": {
|
"node_modules/@nestjs/cli": {
|
||||||
"version": "10.3.2",
|
"version": "10.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz",
|
||||||
|
|
@ -6393,6 +6406,30 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cache-manager": {
|
||||||
|
"version": "5.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.5.1.tgz",
|
||||||
|
"integrity": "sha512-QYZFOjZTTennYdN3NNCKh+yq452+wQ4ChyL40jkEyghIgg5Ugwb4YO8ARIIF1fvTBkgDLlLTYFaxZVaPGmQ92A==",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"lru-cache": "^10.2.0",
|
||||||
|
"promise-coalesce": "^1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cache-manager/node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
||||||
|
},
|
||||||
|
"node_modules/cache-manager/node_modules/lru-cache": {
|
||||||
|
"version": "10.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
|
||||||
|
"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": "14 || >=16.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||||
|
|
@ -9868,14 +9905,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/joi": {
|
"node_modules/joi": {
|
||||||
"version": "17.12.3",
|
"version": "17.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/joi/-/joi-17.6.4.tgz",
|
||||||
"integrity": "sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g==",
|
"integrity": "sha512-tPzkTJHZQjSFCc842QpdVpOZ9LI2txApboNUbW70qgnRB14Lzl+oWQOPdF2N4yqyiY14wBGe8lc7f/2hZxbGmw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/hoek": "^9.3.0",
|
"@hapi/hoek": "^9.0.0",
|
||||||
"@hapi/topo": "^5.1.0",
|
"@hapi/topo": "^5.0.0",
|
||||||
"@sideway/address": "^4.1.5",
|
"@sideway/address": "^4.1.3",
|
||||||
"@sideway/formula": "^3.0.1",
|
"@sideway/formula": "^3.0.0",
|
||||||
"@sideway/pinpoint": "^2.0.0"
|
"@sideway/pinpoint": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -10137,6 +10174,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||||
|
},
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
|
|
@ -11201,6 +11243,14 @@
|
||||||
"asap": "~2.0.3"
|
"asap": "~2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/promise-coalesce": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise-coalesce/-/promise-coalesce-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prompts": {
|
"node_modules/prompts": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,16 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/server": "^4.10.2",
|
"@apollo/server": "^4.10.2",
|
||||||
"@nestjs/apollo": "^12.1.0",
|
"@nestjs/apollo": "^12.1.0",
|
||||||
|
"@nestjs/cache-manager": "^2.2.2",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/graphql": "^12.1.1",
|
"@nestjs/graphql": "^12.1.1",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@prisma/client": "^5.12.1",
|
"@prisma/client": "^5.12.1",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
|
"cache-manager": "^5.5.1",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
"joi": "^17.12.3",
|
"joi": "17.6.4",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,101 @@ model SystemPostMigration {
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Namespace: Auth
|
||||||
|
//
|
||||||
|
model AuthRealm {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
oauth2Clients AuthOauth2Client[]
|
||||||
|
groups IdentityGroup[]
|
||||||
|
profileAttributeNames IdentityProfileAttributeName[]
|
||||||
|
roles AuthRole[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model AuthOauth2Client {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
|
||||||
|
realmId Int
|
||||||
|
realm AuthRealm @relation(fields: [realmId], references: [id])
|
||||||
|
|
||||||
|
clientId String
|
||||||
|
clientSecret String?
|
||||||
|
|
||||||
|
authorizationCodeFlowEnabled Boolean @default(false)
|
||||||
|
resourceOwnerPasswordCredentialsFlowEnabled Boolean @default(false)
|
||||||
|
clientCredentialsFlowEnabled Boolean @default(false)
|
||||||
|
idTokenEnabled Boolean @default(false)
|
||||||
|
refreshTokenEnabled Boolean @default(false)
|
||||||
|
|
||||||
|
scopeMappings AuthOauth2ClientToAuthOauth2Scope[]
|
||||||
|
|
||||||
|
@@unique([realmId, clientId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model AuthOauth2Scope {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
realmId Int
|
||||||
|
scope String
|
||||||
|
|
||||||
|
profileAttributeMappings AuthOauth2ScopeToIdentityProfileAttributeName[]
|
||||||
|
clientMappings AuthOauth2ClientToAuthOauth2Scope[]
|
||||||
|
|
||||||
|
@@unique([realmId, scope])
|
||||||
|
}
|
||||||
|
|
||||||
|
model AuthOauth2ClientToAuthOauth2Scope {
|
||||||
|
clientId Int
|
||||||
|
oauth2Client AuthOauth2Client @relation(fields: [clientId], references: [id])
|
||||||
|
|
||||||
|
scopeId Int
|
||||||
|
scope AuthOauth2Scope @relation(fields: [scopeId], references: [id])
|
||||||
|
|
||||||
|
@@id([clientId, scopeId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model AuthOauth2ScopeToIdentityProfileAttributeName {
|
||||||
|
scopeId Int
|
||||||
|
scope AuthOauth2Scope @relation(fields: [scopeId], references: [id])
|
||||||
|
|
||||||
|
claimName String
|
||||||
|
|
||||||
|
attributeId Int
|
||||||
|
attributes IdentityProfileAttributeName @relation(fields: [attributeId], references: [id])
|
||||||
|
|
||||||
|
@@id([scopeId, attributeId])
|
||||||
|
@@unique([scopeId, claimName])
|
||||||
|
}
|
||||||
|
|
||||||
|
model AuthRole {
|
||||||
|
realmId Int
|
||||||
|
realm AuthRealm @relation(fields: [realmId], references: [id])
|
||||||
|
|
||||||
|
roleName String
|
||||||
|
|
||||||
|
@@id([realmId, roleName])
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Namespace: Identity
|
// Namespace: Identity
|
||||||
//
|
//
|
||||||
|
model EnumIdentityGroupRole {
|
||||||
|
enumValue String @id
|
||||||
|
|
||||||
|
groups IdentityGroup[]
|
||||||
|
}
|
||||||
|
|
||||||
model IdentityGroup {
|
model IdentityGroup {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
isAdmin Boolean @default(false)
|
|
||||||
|
realmId Int
|
||||||
|
realm AuthRealm @relation(fields: [realmId], references: [id])
|
||||||
|
|
||||||
|
role String
|
||||||
|
roleRelation EnumIdentityGroupRole @relation(fields: [role], references: [enumValue])
|
||||||
|
|
||||||
name String?
|
name String?
|
||||||
|
|
||||||
users IdentityGroupToIdentityUser[]
|
users IdentityGroupToIdentityUser[]
|
||||||
|
|
@ -35,9 +124,12 @@ model IdentityGroup {
|
||||||
model IdentityGroupToIdentityUser {
|
model IdentityGroupToIdentityUser {
|
||||||
groupId Int
|
groupId Int
|
||||||
group IdentityGroup @relation(fields: [groupId], references: [id])
|
group IdentityGroup @relation(fields: [groupId], references: [id])
|
||||||
|
|
||||||
userId Int
|
userId Int
|
||||||
user IdentityUser @relation(fields: [userId], references: [id])
|
user IdentityUser @relation(fields: [userId], references: [id])
|
||||||
|
|
||||||
|
userIsGroupAdmin Boolean @default(false)
|
||||||
|
|
||||||
@@id([groupId, userId])
|
@@id([groupId, userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,15 +144,29 @@ model IdentityUser {
|
||||||
authDevices IdentityAuthDevice[]
|
authDevices IdentityAuthDevice[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model IdentityProfileAttributeName {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
|
||||||
|
realmId Int
|
||||||
|
realm AuthRealm @relation(fields: [realmId], references: [id])
|
||||||
|
|
||||||
|
name String
|
||||||
|
|
||||||
|
attributeUses IdentityProfileNonNormalized[]
|
||||||
|
scopeMappings AuthOauth2ScopeToIdentityProfileAttributeName[]
|
||||||
|
}
|
||||||
|
|
||||||
model IdentityProfileNonNormalized {
|
model IdentityProfileNonNormalized {
|
||||||
userId Int
|
userId Int
|
||||||
user IdentityUser @relation(fields: [userId], references: [id])
|
user IdentityUser @relation(fields: [userId], references: [id])
|
||||||
|
|
||||||
hashKey String
|
attributeNameId Int
|
||||||
|
attributeName IdentityProfileAttributeName @relation(fields: [attributeNameId], references: [id])
|
||||||
|
|
||||||
hashValue String
|
hashValue String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
@@id([userId, hashKey])
|
@@id([userId, attributeNameId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model IdentityUserEmails {
|
model IdentityUserEmails {
|
||||||
|
|
@ -98,7 +204,7 @@ model IdentityAuthDevice {
|
||||||
|
|
||||||
model IdentityAuthDeviceNonNormalized {
|
model IdentityAuthDeviceNonNormalized {
|
||||||
authDeviceId Int
|
authDeviceId Int
|
||||||
davResource IdentityAuthDevice @relation(fields: [authDeviceId], references: [id])
|
authDevice IdentityAuthDevice @relation(fields: [authDeviceId], references: [id])
|
||||||
|
|
||||||
hashKey String
|
hashKey String
|
||||||
hashValue String
|
hashValue String
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuthModule } from './auth/auth.module';
|
|
||||||
|
import { AuthorizationServerModule } from './authorization-server/authorization-server.module';
|
||||||
|
import { CacheModule } from './cache/cache.module';
|
||||||
import { ConfigModule } from './config/config.module';
|
import { ConfigModule } from './config/config.module';
|
||||||
import { PrismaModule } from './prisma/prisma.module';
|
|
||||||
import { GraphqlModule } from './graphql/graphql.module';
|
import { GraphqlModule } from './graphql/graphql.module';
|
||||||
|
import { PrismaModule } from './prisma/prisma.module';
|
||||||
|
import { AuthDomainModule } from './auth-domain/auth-domain.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
AuthModule,
|
AuthorizationServerModule,
|
||||||
|
CacheModule,
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
PrismaModule,
|
|
||||||
GraphqlModule,
|
GraphqlModule,
|
||||||
|
PrismaModule,
|
||||||
|
AuthDomainModule,
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
|
|
||||||
@Module({})
|
|
||||||
export class AuthModule {}
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { OauthController } from './oauth/oauth.controller';
|
||||||
|
import { IdentityModule } from '../identity-domain/identity.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
IdentityModule,
|
||||||
|
],
|
||||||
|
controllers: [OauthController]
|
||||||
|
})
|
||||||
|
export class AuthorizationServerModule {}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export interface DeviceHandler {}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PrismaService } from '../../../prisma/prisma.service';
|
||||||
|
import { DeviceHandler } from './device-handler.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PasswordHandler implements DeviceHandler {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly prismaService: PrismaService,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { BadRequestException, Body, Controller, Post } from '@nestjs/common';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { LoginFirstContact, supportedDevices } from './login.interface';
|
||||||
|
import { Identity } from '../../identity-domain/identity.interface';
|
||||||
|
import { TokenManagementService } from '../token-management/token-management.service';
|
||||||
|
import { IdentityAuthDeviceService } from '../../identity-domain/identity-auth-device.service';
|
||||||
|
|
||||||
|
@Controller({
|
||||||
|
version: '1',
|
||||||
|
path: 'auth/:realm',
|
||||||
|
})
|
||||||
|
export class LoginController {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly identityAuthDeviceService: IdentityAuthDeviceService,
|
||||||
|
private readonly tokenManagementService: TokenManagementService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post('login')
|
||||||
|
async login(
|
||||||
|
@Body() body: unknown,
|
||||||
|
): Promise<LoginFirstContact.Response> {
|
||||||
|
|
||||||
|
const { value: request, error } = Joi.object<LoginFirstContact.Request, true>({
|
||||||
|
state: Joi.string().required(),
|
||||||
|
username: Joi.string().required(),
|
||||||
|
}).validate(body, { allowUnknown: false, abortEarly: false });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new BadRequestException(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const authDevices = await this.identityAuthDeviceService.findByUsername(request.username);
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: request.state,
|
||||||
|
availableDevices: authDevices.filter(dev => supportedDevices.includes(dev.deviceType as Identity.AuthDevice.Type)),
|
||||||
|
continuation: await this.tokenManagementService.generateContinuationToken(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('login/authenticate')
|
||||||
|
async loginAuthenticate(
|
||||||
|
|
||||||
|
) {
|
||||||
|
// parseLoginRequestBody
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('logout')
|
||||||
|
async logout() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import { Auth, Identity } from '../../enumerations';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { declare } from '../../utils';
|
||||||
|
|
||||||
|
interface ILoginRequestMethod {
|
||||||
|
device: Identity.AuthDevice.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace LoginFirstContact {
|
||||||
|
export interface Request {
|
||||||
|
state: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
export interface Response {
|
||||||
|
state: string;
|
||||||
|
continuation: string;
|
||||||
|
availableDevices: Identity.AuthDevice.Type[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace LoginPassword {
|
||||||
|
export interface Request extends ILoginRequestMethod {
|
||||||
|
device: Identity.AuthDevice.Type.Password;
|
||||||
|
password: string;
|
||||||
|
state: string;
|
||||||
|
continuation?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Response = Success | TwoFactorRequired | Failure | Timedout | DeviceLocked;
|
||||||
|
|
||||||
|
type Success = {
|
||||||
|
status: Auth.Login.ResponseStatus.Success;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TwoFactorRequired = {
|
||||||
|
status: Auth.Login.ResponseStatus.TwoFactorRequired;
|
||||||
|
state: string;
|
||||||
|
continuation: string;
|
||||||
|
availableDevices: Identity.AuthDevice.Type[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type Failure = {
|
||||||
|
status: Auth.Login.ResponseStatus.Failure;
|
||||||
|
state: string;
|
||||||
|
continuation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Timedout = {
|
||||||
|
status: Auth.Login.ResponseStatus.Timedout;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceLocked = {
|
||||||
|
status: Auth.Login.ResponseStatus.DeviceLocked;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginMethodRequests = LoginPassword.Request;
|
||||||
|
|
||||||
|
export const supportedDevices = [
|
||||||
|
Identity.AuthDevice.Type.Password,
|
||||||
|
]
|
||||||
|
|
||||||
|
const standardJoiValidationOptions: Joi.ValidationOptions = {
|
||||||
|
abortEarly: false,
|
||||||
|
allowUnknown: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginPasswordValidator = Joi.object<LoginPassword.Request, true>({
|
||||||
|
device: Joi.string().required().allow(Identity.AuthDevice.Type.Password),
|
||||||
|
password: Joi.string().required(),
|
||||||
|
state: Joi.string().required(),
|
||||||
|
continuation: Joi.string(),
|
||||||
|
}).options(standardJoiValidationOptions);
|
||||||
|
|
||||||
|
export const parseLoginRequestBody = (body: unknown): [null, LoginMethodRequests] | [Error, null] => {
|
||||||
|
|
||||||
|
const { error: error1, value: simpleRequest } = Joi.object<ILoginRequestMethod, true>({
|
||||||
|
device: Joi.string().required().allow(...supportedDevices),
|
||||||
|
}).validate(body);
|
||||||
|
|
||||||
|
if (error1) {
|
||||||
|
return [error1, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error: error2, value: request } = declare<Joi.ValidationResult, Identity.AuthDevice.Type>()
|
||||||
|
.when(simpleRequest.device)
|
||||||
|
.matches(Identity.AuthDevice.Type.Password).then(() => loginPasswordValidator.validate(body))
|
||||||
|
.resolveOrThrow();
|
||||||
|
|
||||||
|
if (error2) {
|
||||||
|
return [error2, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [null, request];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoginService {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Controller, Get, Post } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Controller({
|
||||||
|
version: '1',
|
||||||
|
path: 'auth/:realm/oauth2',
|
||||||
|
})
|
||||||
|
export class OauthController {
|
||||||
|
|
||||||
|
@Post('authorize')
|
||||||
|
async authorization() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('return')
|
||||||
|
async redirectReturn() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('token')
|
||||||
|
async token() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('user_info')
|
||||||
|
async userInfo() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('jwks_uri')
|
||||||
|
async jwksUri() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('introspection')
|
||||||
|
async introspection() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('revocation_endpoint')
|
||||||
|
async revokeBasic() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('revocation_endpoint')
|
||||||
|
async revoke() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Auth } from '../../enumerations/auth.enumerations';
|
||||||
|
|
||||||
|
interface ErrorResponse {
|
||||||
|
state?: string;
|
||||||
|
error: Auth.Oauth2.Error;
|
||||||
|
error_description?: string;
|
||||||
|
error_uri?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace AuthorizationCode {
|
||||||
|
interface AuthorizationRequest {
|
||||||
|
response_type: Auth.Oauth2.ResponseType.Code;
|
||||||
|
client_id: string;
|
||||||
|
redirect_uri?: string;
|
||||||
|
scope?: string;
|
||||||
|
state?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthorizationResponse {
|
||||||
|
code: string; // 10min redis
|
||||||
|
state?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AccessTokenRequest {
|
||||||
|
grant_type: Auth.Oauth2.AuthorizationGrant.AuthorizationCode;
|
||||||
|
code: string;
|
||||||
|
redirect_uri?: string;
|
||||||
|
client_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AccessTokenResponse {
|
||||||
|
access_token: string;
|
||||||
|
token_type: 'bearer';
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// application/x-www-form-urlencoded
|
||||||
|
// Authorization header required if of type `confidential`
|
||||||
|
// Basic base64(clientId:clientSecret)
|
||||||
|
namespace ResourceOwner {
|
||||||
|
interface AccessTokenRequest {
|
||||||
|
grant_type: Auth.Oauth2.GrantType.Password;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
scope?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AccessTokenResponse {
|
||||||
|
access_token: string;
|
||||||
|
token_type: 'bearer'; // ?
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `confidential` only
|
||||||
|
namespace ClientCredentials {
|
||||||
|
interface AccessTokenRequest {
|
||||||
|
// grant_type: Auth.Oauth2.GrantType.ClientCredentials;
|
||||||
|
scope?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AccessTokenResponse {
|
||||||
|
access_token: string;
|
||||||
|
token_type: 'bearer';
|
||||||
|
expires_in: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { CacheService } from '../../cache/cache.symbols';
|
||||||
|
import { ConfigService } from '../../config/config.service';
|
||||||
|
import { Identity, SystemSettings } from '../../enumerations';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TokenManagementService {
|
||||||
|
|
||||||
|
private readonly signingSecret: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly cacheService: CacheService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async generateContinuationToken(devicesUsed: Identity.AuthDevice.Type[]) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async signToken() {
|
||||||
|
const signingSecret = await this.configService.get(SystemSettings.Auth.TokenManagement.SigningSecret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Identity } from '../../enumerations';
|
||||||
|
|
||||||
|
export namespace TokenManagement {
|
||||||
|
interface GenerateContinuationTokenRequest {
|
||||||
|
deviceUsed: Identity.AuthDevice.Type;
|
||||||
|
identityUserUrn: string;
|
||||||
|
previousContinuationToken?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { CacheModule as NestCacheModule } from '@nestjs/cache-manager';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { CacheService } from './cache.symbols';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
NestCacheModule.register(),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CacheService,
|
||||||
|
useFactory: (cache) => cache,
|
||||||
|
inject: [CacheService],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
exports: [CacheService],
|
||||||
|
})
|
||||||
|
export class CacheModule {}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||||
|
import { Cache } from 'cache-manager';
|
||||||
|
|
||||||
|
export type CacheService = Cache;
|
||||||
|
export const CacheService = CACHE_MANAGER;
|
||||||
|
|
@ -3,13 +3,19 @@ import * as Joi from 'joi';
|
||||||
import { SystemSettings } from '../enumerations';
|
import { SystemSettings } from '../enumerations';
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
|
[SystemSettings.Auth.Oauth2.Enabled]: boolean;
|
||||||
|
[SystemSettings.Auth.Oauth2.EncryptionSecret]: string;
|
||||||
|
[SystemSettings.Auth.TokenManagement.SigningSecret]: string;
|
||||||
[SystemSettings.Graphql.Debug]: boolean;
|
[SystemSettings.Graphql.Debug]: boolean;
|
||||||
[SystemSettings.Graphql.IntrospectionEnabled]: boolean;
|
[SystemSettings.Graphql.IntrospectionEnabled]: boolean;
|
||||||
[SystemSettings.Graphql.PlaygroundEnabled]: boolean;
|
[SystemSettings.Graphql.PlaygroundEnabled]: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const configValidator: Joi.ObjectSchema<Config> = Joi.object<Config, true>({
|
export const configValidator: Joi.ObjectSchema<Config> = Joi.object<Config, true>({
|
||||||
'graphql.debug.enabled': Joi.boolean().required(),
|
[SystemSettings.Auth.Oauth2.Enabled]: Joi.boolean().required(),
|
||||||
'graphql.introspection.enabled': Joi.boolean().required(),
|
[SystemSettings.Auth.Oauth2.EncryptionSecret]: Joi.string().required(),
|
||||||
'graphql.playground.enabled': Joi.boolean().required(),
|
[SystemSettings.Auth.TokenManagement.SigningSecret]: Joi.string().required(),
|
||||||
|
[SystemSettings.Graphql.Debug]: Joi.boolean().required(),
|
||||||
|
[SystemSettings.Graphql.IntrospectionEnabled]: Joi.boolean().required(),
|
||||||
|
[SystemSettings.Graphql.PlaygroundEnabled]: Joi.boolean().required(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthDomainService {}
|
||||||
|
|
@ -6,6 +6,8 @@ import { PrismaService } from './prisma.service';
|
||||||
providers: [
|
providers: [
|
||||||
PrismaService,
|
PrismaService,
|
||||||
],
|
],
|
||||||
exports: [PrismaService],
|
exports: [
|
||||||
|
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class PrismaModule {}
|
export class DomainModule {}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { Identity } from './identity.interface';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class IdentityAuthDeviceService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly prismaService: PrismaService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async findByUsername(username: string): Promise<Identity.AuthDevice[]> {
|
||||||
|
|
||||||
|
const devices = await this.prismaService.identityAuthDevice.findMany({
|
||||||
|
include: {
|
||||||
|
hashMapPairs: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
user: {
|
||||||
|
username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return devices.map(d => Identity.AuthDevice.modelToEntity(d, d.hashMapPairs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class IdentityService {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { IdentityAuthDevice, IdentityAuthDeviceNonNormalized } from '@prisma/client';
|
||||||
|
import { normalizePairs } from '../../utils';
|
||||||
|
|
||||||
|
export namespace Identity {
|
||||||
|
|
||||||
|
export interface AuthDevice {
|
||||||
|
urn: string;
|
||||||
|
userId: number;
|
||||||
|
deviceType: AuthDevice.Type;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace AuthDevice {
|
||||||
|
export enum Type {
|
||||||
|
Password = 'password',
|
||||||
|
ApplicationPassword = 'application_password',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PasswordHashKey {
|
||||||
|
Expiry = 'expiry',
|
||||||
|
PasswordHashString = 'password_hash_string',
|
||||||
|
Locked = 'locked',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Password extends AuthDevice {
|
||||||
|
expiry: Date;
|
||||||
|
passwordHashString: string;
|
||||||
|
locked: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modelToEntity(model: IdentityAuthDevice, hashMapPairs: IdentityAuthDeviceNonNormalized[]): AuthDevice {
|
||||||
|
|
||||||
|
const map = normalizePairs(hashMapPairs);
|
||||||
|
|
||||||
|
const entity: AuthDevice = {
|
||||||
|
urn: `urn:identity:auth-device:${model.id}`,
|
||||||
|
userId: model.userId,
|
||||||
|
deviceType: model.deviceType as Type,
|
||||||
|
createdAt: new Date(model.createdAt),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.deviceType === AuthDevice.Type.Password) {
|
||||||
|
const passwordEntity: Password = {
|
||||||
|
...entity,
|
||||||
|
expiry: new Date(map[PasswordHashKey.Expiry] as string),
|
||||||
|
passwordHashString: map[PasswordHashKey.PasswordHashString] as string,
|
||||||
|
locked: map[PasswordHashKey.Locked] as boolean,
|
||||||
|
}
|
||||||
|
return passwordEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Group {
|
||||||
|
export enum Role {
|
||||||
|
SystemAdmin = 'SYSTEM_ADMIN',
|
||||||
|
RealmAdmin = 'REALM_ADMIN',
|
||||||
|
Standard = 'STANDARD',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PrismaModule } from '../prisma/prisma.module';
|
||||||
|
import { IdentityAuthDeviceService } from './identity-auth-device.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
providers: [
|
||||||
|
IdentityAuthDeviceService,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
IdentityAuthDeviceService,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class IdentityModule {}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
import { _00ApplicationBootstrapDataMigration } from './data-migrations';
|
||||||
|
import { Identity } from './identity-domain/identity.interface';
|
||||||
|
import { CloudDav } from '../enumerations';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.$connect();
|
||||||
|
await this.ensureEnumIntegrity();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onModuleDestroy() {
|
||||||
|
await this.$disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureEnumIntegrity() {
|
||||||
|
|
||||||
|
const prisma = this;
|
||||||
|
|
||||||
|
const enumsRegistered: [string, string[]][] = [
|
||||||
|
['enumIdentityGroupRole', Object.values<string>(Identity.Group.Role)],
|
||||||
|
['enumIdentityAuthDeviceType', Object.values<string>(Identity.AuthDevice.Type)],
|
||||||
|
['enumCloudDavResourceType', Object.values<string>(CloudDav.Resource.Type)],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [dbRunner, known] of enumsRegistered) {
|
||||||
|
await prisma.$transaction(async (tx) => {
|
||||||
|
const result = await tx[dbRunner].findMany();
|
||||||
|
const existing = result.map(e => e.enumValue);
|
||||||
|
const missing = known.filter(k => !existing.includes(k));
|
||||||
|
await tx[dbRunner].createMany({
|
||||||
|
data: missing.map(enumValue => ({ enumValue })),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
export namespace Auth {
|
||||||
|
|
||||||
|
export namespace Login {
|
||||||
|
export enum ResponseStatus {
|
||||||
|
Success = 'success',
|
||||||
|
TwoFactorRequired = '2fa',
|
||||||
|
Failure = 'failure',
|
||||||
|
Timedout = 'timedout',
|
||||||
|
DeviceLocked = 'locked',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Oauth2 {
|
||||||
|
export enum TokenClaims {
|
||||||
|
Issuer = 'iss',
|
||||||
|
Expiry = 'exp',
|
||||||
|
TokenId = 'jwi',
|
||||||
|
IssuedAt = 'iat',
|
||||||
|
}
|
||||||
|
export enum AuthorizationGrant {
|
||||||
|
AuthorizationCode = 'authorization_code',
|
||||||
|
Implicit = 'implicit',
|
||||||
|
ResourceOwnerPasswordCredentials = 'resource_owner_password_credentials',
|
||||||
|
ClientCredentials = 'client_credentials',
|
||||||
|
}
|
||||||
|
export enum GrantType {
|
||||||
|
RefreshToken = 'refresh_token',
|
||||||
|
Password = 'password',
|
||||||
|
}
|
||||||
|
export enum ResponseType {
|
||||||
|
Code = 'code',
|
||||||
|
Token = 'token',
|
||||||
|
}
|
||||||
|
export enum Error {
|
||||||
|
InvalidRequest = 'invalid_request',
|
||||||
|
UnauthorizedClient = 'unauthorized_client',
|
||||||
|
AccessDenied = 'access_denied',
|
||||||
|
UnsupportedResponseType = 'unsupported_response_type',
|
||||||
|
InvalidScope = 'invalid_scope',
|
||||||
|
ServerError = 'server_error',
|
||||||
|
TemporarilyUnavailable = 'temporarily_unavailable',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
export namespace Identity {
|
|
||||||
|
|
||||||
export namespace AuthDevice {
|
|
||||||
export enum Type {
|
|
||||||
Password = 'password',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PasswordHashKey {
|
|
||||||
Expiry = 'expiry',
|
|
||||||
PasswordHashString = 'password_hash_string',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
|
export * from './auth.enumerations';
|
||||||
export * from './cloud-dav.enumerations';
|
export * from './cloud-dav.enumerations';
|
||||||
export * from './identity.enumerations';
|
|
||||||
export * from './system-settings.enumerations';
|
export * from './system-settings.enumerations';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,23 @@
|
||||||
export namespace SystemSettings {
|
export namespace SystemSettings {
|
||||||
|
export namespace Auth {
|
||||||
|
export enum Oauth2 {
|
||||||
|
Enabled = 'auth.oauth2.enabled',
|
||||||
|
EncryptionSecret = 'auth.oauth2.encryption_secret',
|
||||||
|
}
|
||||||
|
export enum TokenManagement {
|
||||||
|
SigningSecret = 'auth.token-management.signing_secret',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export enum Graphql {
|
export enum Graphql {
|
||||||
Debug = 'graphql.debug.enabled',
|
Debug = 'graphql.debug.enabled',
|
||||||
IntrospectionEnabled = 'graphql.introspection.enabled',
|
IntrospectionEnabled = 'graphql.introspection.enabled',
|
||||||
PlaygroundEnabled = 'graphql.playground.enabled',
|
PlaygroundEnabled = 'graphql.playground.enabled',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace Dav {
|
||||||
|
export enum Contacts {
|
||||||
|
Enabled = 'dav.contacts.enabled',
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
import { _00ApplicationBootstrapDataMigration } from './data-migrations';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
|
||||||
async onModuleInit() {
|
|
||||||
await this.$connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
async onModuleDestroy() {
|
|
||||||
await this.$disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
export * from './normalize-hash-set';
|
export * from './normalize-hash-set';
|
||||||
export * from './secure-string';
|
export * from './secure-string';
|
||||||
|
export * from './snake-to-camel';
|
||||||
|
export * from './when-clause';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
// https://stackoverflow.com/questions/40710628/how-to-convert-snake-case-to-camelcase
|
||||||
|
export const snakeToCamel = str =>
|
||||||
|
str.toLowerCase().replace(/([-_][a-z])/g, group =>
|
||||||
|
group
|
||||||
|
.toUpperCase()
|
||||||
|
.replace('-', '')
|
||||||
|
.replace('_', '')
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
type Literal = string | number;
|
||||||
|
type Resolved<T> = T | (() => T);
|
||||||
|
type Then<T, K extends Literal> = (result: Resolved<T>) => WhenClosure<T, K>;
|
||||||
|
|
||||||
|
const defaultErrorMessage = 'Runtime exception: failed to handle all scenarios of when clause';
|
||||||
|
|
||||||
|
class WhenClosure<T, K extends Literal> {
|
||||||
|
|
||||||
|
private readonly stack: {[key: Literal]: Resolved<T>} = {};
|
||||||
|
|
||||||
|
constructor(private readonly expression: Literal) {}
|
||||||
|
|
||||||
|
matches(literal: K): { then: Then<T, K> } {
|
||||||
|
return {
|
||||||
|
then: (result: Resolved<T>) => {
|
||||||
|
this.stack[literal] = result;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else(result: Resolved<T>): T {
|
||||||
|
const resolved = this.expression in this.stack ? this.stack[this.expression] : result;
|
||||||
|
if (resolved instanceof Function) {
|
||||||
|
return resolved();
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveOrThrow(errorMessage = defaultErrorMessage): T {
|
||||||
|
if (this.expression in this.stack) {
|
||||||
|
const resolved = this.stack[this.expression];
|
||||||
|
if (resolved instanceof Function) {
|
||||||
|
return resolved();
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const declare = <T, K extends Literal = Literal>() => ({
|
||||||
|
when: (expression: K) => new WhenClosure<T, K>(expression)
|
||||||
|
});
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import * as request from 'supertest';
|
|
||||||
import { AppModule } from './../src/app.module';
|
|
||||||
|
|
||||||
describe('AppController (e2e)', () => {
|
|
||||||
let app: INestApplication;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
||||||
imports: [AppModule],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
app = moduleFixture.createNestApplication();
|
|
||||||
await app.init();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('/ (GET)', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/')
|
|
||||||
.expect(200)
|
|
||||||
.expect('Hello World!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"moduleFileExtensions": ["js", "json", "ts"],
|
|
||||||
"rootDir": ".",
|
|
||||||
"testEnvironment": "node",
|
|
||||||
"testRegex": ".e2e-spec.ts$",
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue