Skip to content

Commit 2142181

Browse files
committed
refactor(service): users/auth: extract common enrollment function to service layer
1 parent 9284652 commit 2142181

File tree

4 files changed

+96
-65
lines changed

4 files changed

+96
-65
lines changed

service/src/ingress/ingress.app.api.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Avatar, User, UserExpanded, UserIcon } from '../entities/users/entities
44
import { IdentityProviderUser } from './ingress.entities'
55

66

7+
78
export interface EnrollMyselfRequest {
89
username: string
910
password: string
@@ -53,6 +54,18 @@ export interface AdmitFromIdentityProviderOperation {
5354
(req: AdmitFromIdentityProviderRequest): Promise<AppResponse<AdmitFromIdentityProviderResult, EntityNotFoundError | AuthenticationFailedError | InfrastructureError>>
5455
}
5556

57+
export interface UpdateIdentityProviderRequest {
58+
59+
}
60+
61+
export interface UpdateIdentityProviderResult {
62+
63+
}
64+
65+
export interface UpdateIdentityProviderOperation {
66+
(req: UpdateIdentityProviderRequest): Promise<AppResponse<UpdateIdentityProviderResult, PermissionDeniedError | EntityNotFoundError>>
67+
}
68+
5669
export const ErrAuthenticationFailed = Symbol.for('MageError.Ingress.AuthenticationFailed')
5770
export type AuthenticationFailedErrorData = { username: string, identityProviderName: string }
5871
export type AuthenticationFailedError = MageError<typeof ErrAuthenticationFailed, AuthenticationFailedErrorData>

service/src/ingress/ingress.app.impl.ts

Lines changed: 13 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import { entityNotFound, infrastructureError } from '../app.api/app.api.errors'
22
import { AppResponse } from '../app.api/app.api.global'
3-
import { MageEventId } from '../entities/events/entities.events'
4-
import { Team, TeamId } from '../entities/teams/entities.teams'
5-
import { User, UserId, UserRepository, UserRepositoryError } from '../entities/users/entities.users'
3+
import { UserRepository } from '../entities/users/entities.users'
64
import { AdmitFromIdentityProviderOperation, AdmitFromIdentityProviderRequest, authenticationFailedError, EnrollMyselfOperation, EnrollMyselfRequest } from './ingress.app.api'
7-
import { createEnrollmentCandidateUser, IdentityProvider, IdentityProviderRepository, IdentityProviderUser, UserIngressBindingRepository, UserIngressBindings } from './ingress.entities'
5+
import { IdentityProviderRepository, IdentityProviderUser, UserIngressBindingRepository } from './ingress.entities'
6+
import { ProcessNewUserEnrollment } from './ingress.services.api'
87
import { LocalIdpCreateAccountOperation } from './local-idp.app.api'
98
import { JWTService, TokenAssertion } from './verification'
109

1110

12-
export function CreateEnrollMyselfOperation(createLocalIdpAccount: LocalIdpCreateAccountOperation, idpRepo: IdentityProviderRepository, userRepo: UserRepository): EnrollMyselfOperation {
11+
export function CreateEnrollMyselfOperation(createLocalIdpAccount: LocalIdpCreateAccountOperation, idpRepo: IdentityProviderRepository, enrollNewUser: ProcessNewUserEnrollment): EnrollMyselfOperation {
1312
return async function enrollMyself(req: EnrollMyselfRequest): ReturnType<EnrollMyselfOperation> {
1413
const localAccountCreate = await createLocalIdpAccount(req)
1514
if (localAccountCreate.error) {
1615
return AppResponse.error(localAccountCreate.error)
1716
}
1817
const localAccount = localAccountCreate.success!
19-
const candidateMageAccount: Partial<User> = {
18+
const candidateMageAccount: IdentityProviderUser = {
2019
username: localAccount.username,
2120
displayName: req.displayName,
21+
phones: [],
2222
}
2323
if (req.email) {
2424
candidateMageAccount.email = req.email
@@ -27,69 +27,17 @@ export function CreateEnrollMyselfOperation(createLocalIdpAccount: LocalIdpCreat
2727
candidateMageAccount.phones = [ { number: req.phone, type: 'Main' } ]
2828
}
2929
const localIdp = await idpRepo.findIdpByName('local')
30+
if (!localIdp) {
31+
throw new Error('local idp not found')
32+
}
33+
const enrollmentResult = await enrollNewUser(candidateMageAccount, localIdp)
34+
3035
// TODO: auto-activate account after enrollment policy
3136
throw new Error('unimplemented')
3237
}
3338
}
3439

35-
export interface AssignTeamMember {
36-
(member: UserId, team: TeamId): Promise<boolean>
37-
}
38-
39-
export interface FindEventTeam {
40-
(mageEventId: MageEventId): Promise<Team | null>
41-
}
42-
43-
async function enrollNewUser(idpAccount: IdentityProviderUser, idp: IdentityProvider, userRepo: UserRepository, ingressBindingRepo: UserIngressBindingRepository, findEventTeam: FindEventTeam, assignTeamMember: AssignTeamMember): Promise<{ mageAccount: User, ingressBindings: UserIngressBindings }> {
44-
console.info(`enrolling new user account ${idpAccount.username} from identity provider ${idp.name}`)
45-
const candidate = createEnrollmentCandidateUser(idpAccount, idp)
46-
const mageAccount = await userRepo.create(candidate)
47-
if (mageAccount instanceof UserRepositoryError) {
48-
throw mageAccount
49-
}
50-
const ingressBindings = await ingressBindingRepo.saveUserIngressBinding(
51-
mageAccount.id,
52-
{
53-
userId: mageAccount.id,
54-
idpId: idp.id,
55-
idpAccountId: idpAccount.username,
56-
idpAccountAttrs: {},
57-
// TODO: these do not have functionality yet
58-
verified: true,
59-
enabled: true,
60-
}
61-
)
62-
if (ingressBindings instanceof Error) {
63-
throw ingressBindings
64-
}
65-
const { assignToTeams, assignToEvents } = idp.userEnrollmentPolicy
66-
const assignEnrolledToTeam = (teamId: TeamId): Promise<{ teamId: TeamId, assigned: boolean }> => {
67-
return assignTeamMember(mageAccount.id, teamId)
68-
.then(assigned => ({ teamId, assigned }))
69-
.catch(err => {
70-
console.error(`error assigning enrolled user ${mageAccount.username} to team ${teamId}`, err)
71-
return { teamId, assigned: false }
72-
})
73-
}
74-
const assignEnrolledToEventTeam = (eventId: MageEventId): Promise<{ eventId: MageEventId, teamId: TeamId | null, assigned: boolean }> => {
75-
return findEventTeam(eventId)
76-
.then<{ eventId: MageEventId, teamId: TeamId | null, assigned: boolean }>(eventTeam => {
77-
if (eventTeam) {
78-
return assignEnrolledToTeam(eventTeam.id).then(teamAssignment => ({ eventId, ...teamAssignment }))
79-
}
80-
console.error(`failed to find implicit team for event ${eventId} while enrolling user ${mageAccount.username}`)
81-
return { eventId, teamId: null, assigned: false }
82-
})
83-
.catch(err => {
84-
console.error(`error looking up implicit team for event ${eventId} while enrolling user ${mageAccount.username}`, err)
85-
return { eventId, teamId: null, assigned: false }
86-
})
87-
}
88-
await Promise.all([ ...assignToTeams.map(assignEnrolledToTeam), ...assignToEvents.map(assignEnrolledToEventTeam) ])
89-
return { mageAccount, ingressBindings }
90-
}
91-
92-
export function CreateAdmitFromIdentityProviderOperation(idpRepo: IdentityProviderRepository, ingressBindingRepo: UserIngressBindingRepository, userRepo: UserRepository, findEventTeam: FindEventTeam, assignTeamMember: AssignTeamMember, tokenService: JWTService): AdmitFromIdentityProviderOperation {
40+
export function CreateAdmitFromIdentityProviderOperation(idpRepo: IdentityProviderRepository, ingressBindingRepo: UserIngressBindingRepository, userRepo: UserRepository, enrollNewUser: ProcessNewUserEnrollment, tokenService: JWTService): AdmitFromIdentityProviderOperation {
9341
return async function admitFromIdentityProvider(req: AdmitFromIdentityProviderRequest): ReturnType<AdmitFromIdentityProviderOperation> {
9442
const idp = await idpRepo.findIdpByName(req.identityProviderName)
9543
if (!idp) {
@@ -104,7 +52,7 @@ export function CreateAdmitFromIdentityProviderOperation(idpRepo: IdentityProvid
10452
return { mageAccount: existingAccount, ingressBindings }
10553
})
10654
}
107-
return enrollNewUser(idpAccount, idp, userRepo, ingressBindingRepo, findEventTeam, assignTeamMember)
55+
return enrollNewUser(idpAccount, idp)
10856
})
10957
.then(enrolled => {
11058
const { mageAccount, ingressBindings } = enrolled
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { User } from '../entities/users/entities.users'
2+
import { IdentityProvider, IdentityProviderUser, UserIngressBindings } from './ingress.entities'
3+
4+
export interface ProcessNewUserEnrollment {
5+
(idpAccount: IdentityProviderUser, idp: IdentityProvider): Promise<{ mageAccount: User, ingressBindings: UserIngressBindings }>
6+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { MageEventId } from '../entities/events/entities.events'
2+
import { Team, TeamId } from '../entities/teams/entities.teams'
3+
import { User, UserId, UserRepository, UserRepositoryError } from '../entities/users/entities.users'
4+
import { createEnrollmentCandidateUser, IdentityProvider, IdentityProviderUser, UserIngressBindingRepository, UserIngressBindings } from './ingress.entities'
5+
import { ProcessNewUserEnrollment } from './ingress.services.api'
6+
7+
export interface AssignTeamMember {
8+
(member: UserId, team: TeamId): Promise<boolean>
9+
}
10+
11+
export interface FindEventTeam {
12+
(mageEventId: MageEventId): Promise<Team | null>
13+
}
14+
15+
export function CreateProcessNewUserEnrollmentService(userRepo: UserRepository, ingressBindingRepo: UserIngressBindingRepository, findEventTeam: FindEventTeam, assignTeamMember: AssignTeamMember): ProcessNewUserEnrollment {
16+
return async function processNewUserEnrollment(idpAccount: IdentityProviderUser, idp: IdentityProvider): Promise<{ mageAccount: User, ingressBindings: UserIngressBindings }> {
17+
console.info(`enrolling new user account ${idpAccount.username} from identity provider ${idp.name}`)
18+
const candidate = createEnrollmentCandidateUser(idpAccount, idp)
19+
const mageAccount = await userRepo.create(candidate)
20+
if (mageAccount instanceof UserRepositoryError) {
21+
throw mageAccount
22+
}
23+
const ingressBindings = await ingressBindingRepo.saveUserIngressBinding(
24+
mageAccount.id,
25+
{
26+
userId: mageAccount.id,
27+
idpId: idp.id,
28+
idpAccountId: idpAccount.username,
29+
idpAccountAttrs: {},
30+
// TODO: these do not have functionality yet
31+
verified: true,
32+
enabled: true,
33+
}
34+
)
35+
if (ingressBindings instanceof Error) {
36+
throw ingressBindings
37+
}
38+
const { assignToTeams, assignToEvents } = idp.userEnrollmentPolicy
39+
const assignEnrolledToTeam = (teamId: TeamId): Promise<{ teamId: TeamId, assigned: boolean }> => {
40+
return assignTeamMember(mageAccount.id, teamId)
41+
.then(assigned => ({ teamId, assigned }))
42+
.catch(err => {
43+
console.error(`error assigning enrolled user ${mageAccount.username} to team ${teamId}`, err)
44+
return { teamId, assigned: false }
45+
})
46+
}
47+
const assignEnrolledToEventTeam = (eventId: MageEventId): Promise<{ eventId: MageEventId, teamId: TeamId | null, assigned: boolean }> => {
48+
return findEventTeam(eventId)
49+
.then<{ eventId: MageEventId, teamId: TeamId | null, assigned: boolean }>(eventTeam => {
50+
if (eventTeam) {
51+
return assignEnrolledToTeam(eventTeam.id).then(teamAssignment => ({ eventId, ...teamAssignment }))
52+
}
53+
console.error(`failed to find implicit team for event ${eventId} while enrolling user ${mageAccount.username}`)
54+
return { eventId, teamId: null, assigned: false }
55+
})
56+
.catch(err => {
57+
console.error(`error looking up implicit team for event ${eventId} while enrolling user ${mageAccount.username}`, err)
58+
return { eventId, teamId: null, assigned: false }
59+
})
60+
}
61+
await Promise.all([ ...assignToTeams.map(assignEnrolledToTeam), ...assignToEvents.map(assignEnrolledToEventTeam) ])
62+
return { mageAccount, ingressBindings }
63+
}
64+
}

0 commit comments

Comments
 (0)