alp-node-auth
Version:
authentication with alp
1 lines • 70.3 kB
Source Map (JSON)
{"version":3,"file":"index-node20.mjs","sources":["../src/createAuthController.ts","../src/createRoutes.ts","../src/utils/generators.ts","../src/services/authentification/AuthenticationService.ts","../src/services/user/UserAccountsService.ts","../src/utils/cookies.ts","../src/utils/createFindLoggedInUser.ts","../src/MongoUsersManager.ts","../src/services/user/UserAccountGoogleService.ts","../src/services/user/UserAccountSlackService.ts","../src/authSocketIO.ts","../src/authApolloContext.ts","../src/index.ts"],"sourcesContent":["import type { AlpRouteRef, Context } from \"alp-node\";\nimport type MongoUsersManager from \"./MongoUsersManager\";\nimport type {\n AccessResponseHooks,\n AuthenticationService,\n} from \"./services/authentification/AuthenticationService\";\nimport type {\n AllowedMapParamsStrategy,\n AllowedStrategyKeys,\n} from \"./services/authentification/types\";\nimport type { User, UserSanitized } from \"./types\";\n\nexport interface CreateAuthControllerParams<\n StrategyKeys extends AllowedStrategyKeys,\n U extends User = User,\n USanitized extends UserSanitized = UserSanitized,\n> {\n authenticationService: AuthenticationService<StrategyKeys, U, UserSanitized>;\n homeRouterKey?: string;\n usersManager: MongoUsersManager<U, USanitized>;\n defaultStrategy?: StrategyKeys;\n authHooks?: AuthHooks<StrategyKeys>;\n}\n\nexport interface AuthController {\n login: AlpRouteRef;\n addScope: AlpRouteRef;\n response: AlpRouteRef;\n logout: AlpRouteRef;\n}\n\ntype OptionalRecord<K extends keyof any, T> = Partial<Record<K, T>>;\n\nexport interface AuthHooks<StrategyKeys extends AllowedStrategyKeys>\n extends AccessResponseHooks<StrategyKeys> {\n paramsForLogin?: <StrategyKey extends StrategyKeys>(\n strategy: StrategyKey,\n ctx: Context,\n ) =>\n | OptionalRecord<AllowedMapParamsStrategy[StrategyKey], any>\n | Promise<OptionalRecord<AllowedMapParamsStrategy[StrategyKey], any>>\n | Promise<void>\n // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n | void;\n}\n\nexport function createAuthController<\n StrategyKeys extends AllowedStrategyKeys,\n U extends User = User,\n USanitized extends UserSanitized = UserSanitized,\n>({\n usersManager,\n authenticationService,\n homeRouterKey = \"/\",\n defaultStrategy,\n authHooks = {},\n}: CreateAuthControllerParams<StrategyKeys, U, USanitized>): AuthController {\n return {\n async login(ctx: Context): Promise<void> {\n const strategy: StrategyKeys = (ctx.namedRouteParam(\"strategy\") ||\n defaultStrategy) as StrategyKeys;\n if (!strategy) throw new Error(\"Strategy missing\");\n const params =\n (authHooks.paramsForLogin &&\n (await authHooks.paramsForLogin(strategy, ctx))) ||\n {};\n await authenticationService.redirectAuthUrl(ctx, strategy, {}, params);\n },\n\n /**\n * Add scope in existing\n * The user must already be connected\n */\n async addScope(ctx: Context): Promise<void> {\n if (!ctx.state.loggedInUser) {\n ctx.redirectTo(homeRouterKey);\n return;\n }\n\n const strategy: StrategyKeys = (ctx.namedRouteParam(\"strategy\") ||\n defaultStrategy) as StrategyKeys;\n if (!strategy) throw new Error(\"Strategy missing\");\n const scopeKey = ctx.namedRouteParam(\"scopeKey\");\n if (!scopeKey) throw new Error(\"Scope missing\");\n await authenticationService.redirectAuthUrl(ctx, strategy, { scopeKey });\n },\n\n async response(ctx: Context): Promise<void> {\n const strategy: StrategyKeys = ctx.namedRouteParam(\n \"strategy\",\n ) as StrategyKeys;\n ctx.assert(strategy);\n\n const loggedInUser = await authenticationService.accessResponse(\n ctx,\n strategy,\n !!ctx.state.loggedInUser,\n {\n afterLoginSuccess: authHooks.afterLoginSuccess,\n afterScopeUpdate: authHooks.afterScopeUpdate,\n },\n );\n const keyPath = usersManager.store.keyPath;\n await ctx.setLoggedIn(loggedInUser[keyPath], loggedInUser);\n ctx.redirectTo(homeRouterKey);\n },\n\n // eslint-disable-next-line @typescript-eslint/require-await -- keep async in case i later need await in this method\n async logout(ctx: Context): Promise<void> {\n ctx.logout();\n ctx.redirectTo(homeRouterKey);\n },\n };\n}\n","import type { AuthController } from \"./createAuthController\";\n\nexport interface AuthRoutes {\n login: [string, (segment: any) => void];\n addScope: [string, AuthController[\"addScope\"]];\n logout: [string, AuthController[\"logout\"]];\n}\n\nexport const createRoutes = (controller: AuthController): AuthRoutes => ({\n login: [\n \"/login/:strategy?\",\n (segment: any) => {\n segment.add(\"/response\", controller.response, \"authResponse\");\n segment.defaultRoute(controller.login, \"login\");\n },\n ],\n addScope: [\"/add-scope/:strategy/:scopeKey\", controller.addScope],\n logout: [\"/logout\", controller.logout],\n});\n","import { randomBytes } from \"node:crypto\";\nimport { promisify } from \"node:util\";\n\nconst randomBytesPromisified = promisify(randomBytes);\n\nexport async function randomBase64(size: number): Promise<string> {\n const buffer = await randomBytesPromisified(size);\n return buffer.toString(\"base64\");\n}\n\nexport async function randomHex(size: number): Promise<string> {\n const buffer = await randomBytesPromisified(size);\n return buffer.toString(\"hex\");\n}\n","/* eslint-disable @typescript-eslint/no-unsafe-argument */\n/* eslint-disable @typescript-eslint/explicit-module-boundary-types */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable camelcase */\nimport { EventEmitter } from \"node:events\";\nimport type { Context, NodeConfig } from \"alp-node\";\nimport { Logger } from \"nightingale-logger\";\nimport type { Strategy as Oauth2Strategy } from \"../../../strategies/strategies.d\";\nimport type { Account, AccountId, User, UserSanitized } from \"../../types\";\nimport { randomHex } from \"../../utils/generators\";\nimport type UserAccountsService from \"../user/UserAccountsService\";\nimport type { AllowedStrategyKeys, Tokens } from \"./types\";\n\nconst logger = new Logger(\"alp:auth:authentication\");\n\nexport interface GenerateAuthUrlOptions {\n accessType?: string;\n grantType?: string;\n includeGrantedScopes?: boolean;\n loginHint?: string;\n prompt?: string;\n redirectUri?: string;\n scope?: string;\n state?: string;\n}\n\nexport interface GetTokensOptions {\n code: string;\n redirectUri: string;\n}\n\nexport type Strategies<StrategyKeys extends AllowedStrategyKeys> = Record<\n StrategyKeys,\n Oauth2Strategy<any>\n>;\n\nexport interface AccessResponseHooks<StrategyKeys, U extends User = User> {\n afterLoginSuccess?: <StrategyKey extends StrategyKeys>(\n strategy: StrategyKey,\n loggedInUser: U,\n ) => Promise<void> | void;\n\n afterScopeUpdate?: <StrategyKey extends StrategyKeys>(\n strategy: StrategyKey,\n scopeKey: string,\n account: Account,\n user: U,\n ) => Promise<void> | void;\n}\n\nexport class AuthenticationService<\n StrategyKeys extends AllowedStrategyKeys,\n U extends User = User,\n USanitized extends UserSanitized = UserSanitized,\n // eslint-disable-next-line unicorn/prefer-event-target\n> extends EventEmitter {\n config: NodeConfig;\n\n strategies: Strategies<StrategyKeys>;\n\n userAccountsService: UserAccountsService<StrategyKeys, U, USanitized>;\n\n constructor(\n config: NodeConfig,\n strategies: Strategies<StrategyKeys>,\n userAccountsService: UserAccountsService<StrategyKeys, U, USanitized>,\n ) {\n super();\n this.config = config;\n this.strategies = strategies;\n this.userAccountsService = userAccountsService;\n }\n\n generateAuthUrl<T extends StrategyKeys>(strategy: T, params: any): string {\n logger.debug(\"generateAuthUrl\", { strategy, params });\n const strategyInstance = this.strategies[strategy];\n switch (strategyInstance.type) {\n case \"oauth2\":\n return strategyInstance.oauth2.authorizationCode.authorizeURL(params);\n default:\n throw new Error(\"Invalid strategy\");\n }\n }\n\n async getTokens(\n strategy: StrategyKeys,\n options: GetTokensOptions,\n ): Promise<Tokens> {\n logger.debug(\"getTokens\", { strategy, options });\n const strategyInstance = this.strategies[strategy];\n switch (strategyInstance.type) {\n case \"oauth2\": {\n const result = await strategyInstance.oauth2.authorizationCode.getToken(\n {\n code: options.code,\n redirect_uri: options.redirectUri,\n },\n );\n if (!result) return result;\n const tokens = result.token;\n\n return {\n accessToken: tokens.access_token as string,\n refreshToken: tokens.refresh_token as string,\n tokenType: tokens.token_type as string,\n expiresIn: tokens.expires_in as number,\n expireDate: (() => {\n if (tokens.expires_in == null) return null;\n const d = new Date();\n d.setTime(d.getTime() + (tokens.expires_in as number) * 1000);\n return d;\n })(),\n idToken: tokens.id_token as string,\n };\n // return strategyInstance.accessToken.create(result);\n }\n\n default:\n throw new Error(\"Invalid stategy\");\n }\n }\n\n async refreshToken(\n strategy: StrategyKeys,\n tokensParam: { refreshToken: string },\n ): Promise<Tokens> {\n logger.debug(\"refreshToken\", { strategy });\n if (!tokensParam.refreshToken) {\n throw new Error(\"Missing refresh token\");\n }\n const strategyInstance = this.strategies[strategy];\n switch (strategyInstance.type) {\n case \"oauth2\": {\n const token = strategyInstance.oauth2.clientCredentials.createToken({\n refresh_token: tokensParam.refreshToken,\n });\n const result = await token.refresh();\n const tokens = result.token;\n return {\n accessToken: tokens.access_token as string,\n tokenType: tokens.token_type as string,\n expiresIn: tokens.expires_in as number,\n expireDate: (() => {\n if (tokens.expires_in == null) return null;\n const d = new Date();\n d.setTime(d.getTime() + (tokens.expires_in as number) * 1000);\n return d;\n })(),\n idToken: tokens.id_token as string,\n };\n }\n\n default:\n throw new Error(\"Invalid stategy\");\n }\n }\n\n redirectUri(ctx: Context, strategy: string): string {\n const host = `http${this.config.get(\"allowHttps\") ? \"s\" : \"\"}://${\n ctx.request.host\n }`;\n return `${host}${ctx.urlGenerator(\"authResponse\", {\n strategy,\n })}`;\n }\n\n async redirectAuthUrl(\n ctx: Context,\n strategy: StrategyKeys,\n {\n refreshToken,\n scopeKey,\n user,\n accountId,\n }: {\n refreshToken?: string | undefined;\n scopeKey?: string | undefined;\n user?: U;\n accountId?: AccountId;\n },\n params?: any,\n ): Promise<void> {\n logger.debug(\"redirectAuthUrl\", { strategy, scopeKey, refreshToken });\n const state = await randomHex(8);\n const isLoginAccess = !scopeKey || scopeKey === \"login\";\n const scope = this.userAccountsService.getScope(\n strategy,\n scopeKey || \"login\",\n user,\n accountId,\n );\n\n if (!scope) {\n throw new Error(\"Invalid empty scope\");\n }\n\n ctx.cookies.set(\n `auth_${strategy}_${state}`,\n JSON.stringify({\n scopeKey,\n scope,\n isLoginAccess,\n }),\n {\n maxAge: 10 * 60 * 1000,\n httpOnly: true,\n secure: this.config.get(\"allowHttps\"),\n },\n );\n const redirectUri = this.generateAuthUrl(strategy, {\n redirect_uri: this.redirectUri(ctx, strategy),\n scope,\n state,\n access_type: refreshToken ? \"offline\" : \"online\",\n ...params,\n });\n\n ctx.redirect(redirectUri);\n }\n\n async accessResponse<StrategyKey extends StrategyKeys>(\n ctx: Context,\n strategy: StrategyKey,\n isLoggedIn: boolean,\n hooks: AccessResponseHooks<StrategyKeys, U>,\n ): Promise<U> {\n const errorParam = ctx.params.queryParam(\"error\").notEmpty();\n if (errorParam.isValid()) {\n ctx.throw(403, errorParam.value);\n }\n\n const code = ctx.validParams.queryParam(\"code\").notEmpty().value;\n const state = ctx.validParams.queryParam(\"state\").notEmpty().value;\n\n const cookieName = `auth_${strategy}_${state}`;\n const cookie = ctx.cookies.get(cookieName);\n ctx.cookies.set(cookieName, \"\", { expires: new Date(1) });\n if (!cookie) {\n throw new Error(\"No cookie for this state\");\n }\n\n const parsedCookie = JSON.parse(cookie);\n if (!parsedCookie?.scope) {\n throw new Error(\"Unexpected cookie value\");\n }\n\n if (!parsedCookie.isLoginAccess) {\n if (!isLoggedIn) {\n throw new Error(\"You are not connected\");\n }\n }\n\n const tokens: Tokens = await this.getTokens(strategy, {\n code,\n redirectUri: this.redirectUri(ctx, strategy),\n });\n\n if (parsedCookie.isLoginAccess) {\n const user = await this.userAccountsService.findOrCreateFromStrategy(\n strategy,\n tokens,\n parsedCookie.scope,\n parsedCookie.scopeKey,\n );\n\n if (hooks.afterLoginSuccess) {\n await hooks.afterLoginSuccess(strategy, user);\n }\n\n return user;\n }\n\n const loggedInUser = ctx.state.loggedInUser as U;\n const { account, user } = await this.userAccountsService.update(\n loggedInUser,\n strategy,\n tokens,\n parsedCookie.scope,\n parsedCookie.scopeKey,\n );\n\n if (hooks.afterScopeUpdate) {\n await hooks.afterScopeUpdate(\n strategy,\n parsedCookie.scopeKey,\n account,\n user,\n );\n }\n\n return loggedInUser;\n }\n\n refreshAccountTokens(user: U, account: Account): Promise<boolean> {\n if (\n account.tokenExpireDate &&\n account.tokenExpireDate.getTime() > Date.now()\n ) {\n return Promise.resolve(false);\n }\n return this.refreshToken(account.provider as StrategyKeys, {\n // accessToken: account.accessToken,\n refreshToken: account.refreshToken!,\n }).then((tokens: Tokens) => {\n if (!tokens) {\n // serviceGoogle.updateFields({ accessToken:null, refreshToken:null, status: .OUTDATED });\n return false;\n }\n account.accessToken = tokens.accessToken;\n account.tokenExpireDate = tokens.expireDate;\n return this.userAccountsService\n .updateAccount(user, account)\n .then(() => true);\n });\n }\n}\n","import { EventEmitter } from \"node:events\";\nimport { Logger } from \"nightingale-logger\";\nimport type MongoUsersManager from \"../../MongoUsersManager\";\nimport type { Account, AccountId, User, UserSanitized } from \"../../types\";\nimport type { AllowedStrategyKeys } from \"../authentification/types\";\nimport type { AccountService, TokensObject } from \"./types\";\n\nconst logger = new Logger(\"alp:auth:userAccounts\");\n\nexport const STATUSES = {\n VALIDATED: \"validated\",\n DELETED: \"deleted\",\n};\n\nexport default class UserAccountsService<\n StrategyKeys extends AllowedStrategyKeys,\n U extends User = User,\n USanitized extends UserSanitized = UserSanitized,\n // eslint-disable-next-line unicorn/prefer-event-target\n> extends EventEmitter {\n private readonly strategyToService: Record<StrategyKeys, AccountService<any>>;\n\n usersManager: MongoUsersManager<U, USanitized>;\n\n constructor(\n usersManager: MongoUsersManager<U, USanitized>,\n strategyToService: Record<StrategyKeys, AccountService<any>>,\n ) {\n super();\n this.usersManager = usersManager;\n this.strategyToService = strategyToService;\n }\n\n getScope(\n strategy: StrategyKeys,\n scopeKey: string,\n user?: U,\n accountId?: AccountId,\n ): string {\n logger.debug(\"getScope\", { strategy, userId: user?._id });\n const service = this.strategyToService[strategy];\n if (!service) {\n throw new Error(\"Strategy not supported\");\n }\n\n const newScope = service.scopeKeyToScope[scopeKey]!;\n if (!user || !accountId) {\n return newScope;\n }\n const account = user.accounts.find(\n (account) =>\n account.provider === strategy && account.accountId === accountId,\n );\n\n if (!account) {\n throw new Error(\"Could not found associated account\");\n }\n return service.getScope(account.scope, newScope).join(\" \");\n }\n\n async update(\n user: U,\n strategy: StrategyKeys,\n tokens: TokensObject,\n scope: string,\n subservice: string,\n ): Promise<{ user: U; account: U[\"accounts\"][number] }> {\n const service = this.strategyToService[strategy];\n const profile = await service.getProfile(tokens);\n const accountId = service.getId(profile);\n const account = user.accounts.find(\n (account) =>\n account.provider === strategy && account.accountId === accountId,\n );\n if (!account) {\n // TODO check if already exists in other user => merge\n // TODO else add a new account in this user\n throw new Error(\"Could not found associated account\");\n }\n account.status = \"valid\";\n account.accessToken = tokens.accessToken;\n if (tokens.refreshToken) {\n account.refreshToken = tokens.refreshToken;\n }\n if (tokens.expireDate !== undefined) {\n account.tokenExpireDate = tokens.expireDate;\n }\n account.scope = service.getScope(account.scope, scope);\n account.subservices = account.subservices || [];\n if (subservice && !account.subservices.includes(subservice)) {\n account.subservices.push(subservice);\n }\n\n await this.usersManager.replaceOne(user);\n return { user, account };\n }\n\n async findOrCreateFromStrategy(\n strategy: StrategyKeys,\n tokens: TokensObject,\n scope: string,\n subservice: string,\n ): Promise<U> {\n const service = this.strategyToService[strategy];\n if (!service) throw new Error(\"Strategy not supported\");\n\n const profile = await service.getProfile(tokens);\n const accountId = service.getId(profile);\n if (!accountId) throw new Error(\"Invalid profile: no id found\");\n\n const emails = service.getEmails(profile);\n\n let user: Partial<U> | undefined =\n await this.usersManager.findOneByAccountOrEmails({\n provider: service.providerKey,\n accountId,\n emails,\n });\n\n logger.info(!user ? \"create user\" : \"existing user\", {\n userId: user?._id,\n accountId,\n /*emails , user*/\n });\n\n if (!user) {\n user = {};\n }\n\n Object.assign(user, {\n displayName: service.getDisplayName(profile),\n fullName: service.getFullName(profile),\n status: STATUSES.VALIDATED,\n });\n\n if (!user.accounts) user.accounts = [];\n\n let account: Partial<Account> | undefined = user.accounts.find(\n (account: Account) =>\n account.provider === strategy && account.accountId === accountId,\n );\n\n if (!account) {\n account = { provider: strategy, accountId };\n user.accounts.push(account as Account);\n }\n\n account.name = service.getAccountName(profile);\n account.status = \"valid\";\n account.profile = profile;\n account.accessToken = tokens.accessToken;\n if (tokens.refreshToken) {\n account.refreshToken = tokens.refreshToken;\n }\n if (tokens.expireDate !== undefined) {\n account.tokenExpireDate = tokens.expireDate;\n }\n account.scope = service.getScope(account.scope, scope);\n\n if (!account.subservices) account.subservices = [];\n if (subservice && !account.subservices.includes(subservice)) {\n account.subservices.push(subservice);\n }\n\n if (!user.emails) user.emails = [];\n const userEmails = user.emails;\n emails.forEach((email: string) => {\n if (!userEmails.includes(email)) {\n userEmails.push(email);\n }\n });\n\n user.emailDomains = [\n // eslint-disable-next-line unicorn/no-array-reduce\n ...user.emails.reduce(\n (domains: Set<string>, email: string) =>\n domains.add(email.split(\"@\", 2)[1]!),\n new Set<string>(),\n ),\n ];\n\n const keyPath = this.usersManager.store.keyPath;\n\n if (user[keyPath]) {\n await this.usersManager.replaceOne(user as U);\n } else {\n await this.usersManager.insertOne(user as U);\n }\n\n return user as U;\n }\n\n async updateAccount(user: U, account: Account): Promise<U> {\n await this.usersManager.updateAccount(user, account);\n return user;\n }\n}\n","import type { IncomingMessage } from \"node:http\";\nimport type { Option } from \"cookies\";\nimport Cookies from \"cookies\";\n\nexport const COOKIE_NAME_TOKEN = \"loggedInUserToken\";\nexport const COOKIE_NAME_STATE = \"loggedInUserState\";\n\nexport const getTokenFromRequest = (\n req: IncomingMessage,\n options?: Pick<Option, Exclude<keyof Option, \"secure\">>,\n): string | undefined => {\n if (req.headers.authorization?.startsWith(\"Bearer \")) {\n return req.headers.authorization.slice(\"Bearer \".length);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const cookies = new Cookies(req, null as unknown as any, {\n ...options,\n secure: true,\n });\n\n return cookies.get(COOKIE_NAME_TOKEN);\n};\n","import { promisify } from \"node:util\";\nimport type {\n GetPublicKeyOrSecret,\n Secret,\n VerifyCallback,\n VerifyOptions,\n} from \"jsonwebtoken\";\nimport jsonwebtoken from \"jsonwebtoken\";\nimport type { Logger } from \"nightingale-logger\";\nimport type MongoUsersManager from \"../MongoUsersManager\";\nimport type { User, UserSanitized } from \"../types\";\n\ntype Verify = (\n token: string,\n secretOrPublicKey: GetPublicKeyOrSecret | Secret,\n options?: VerifyOptions,\n callback?: VerifyCallback,\n) => void;\n\nconst verifyPromisified = promisify<\n Parameters<Verify>[0],\n Parameters<Verify>[1],\n Parameters<Verify>[2],\n Parameters<VerifyCallback>[1]\n>(jsonwebtoken.verify as Verify);\n\nconst createDecodeJWT =\n (secretKey: string) =>\n async (token: string, jwtAudience: string): Promise<string | undefined> => {\n const result = await verifyPromisified(token, secretKey, {\n algorithms: [\"HS512\"],\n audience: jwtAudience,\n });\n return (result as any)?.loggedInUserId as string | undefined;\n };\n\nexport type FindLoggedInUser<U extends User> = (\n jwtAudience?: string,\n token?: string,\n) => Promise<[U[\"_id\"] | null | undefined, U | null | undefined]>;\n\nexport const createFindLoggedInUser = <\n U extends User,\n USanitized extends UserSanitized,\n>(\n secretKey: string,\n usersManager: MongoUsersManager<U, USanitized>,\n logger: Logger,\n): FindLoggedInUser<U> => {\n const decodeJwt = createDecodeJWT(secretKey);\n\n const findLoggedInUser: FindLoggedInUser<U> = async (jwtAudience, token) => {\n if (!token || !jwtAudience) return [null, null];\n\n let loggedInUserId;\n try {\n loggedInUserId = await decodeJwt(token, jwtAudience);\n } catch (error: unknown) {\n logger.debug(\"failed to verify authentification\", { err: error });\n }\n\n if (loggedInUserId == null) return [null, null];\n\n const loggedInUser = await usersManager.findById(loggedInUserId);\n\n if (!loggedInUser) return [null, null];\n\n return [loggedInUserId, loggedInUser];\n };\n\n return findLoggedInUser;\n};\n","import type { MongoInsertType, MongoStore, Update } from \"liwi-mongo\";\nimport type { Account, User, UserSanitized } from \"./types\";\n\nexport default class MongoUsersManager<\n U extends User = User,\n USanitized extends UserSanitized = UserSanitized,\n> {\n store: MongoStore<U>;\n\n constructor(store: MongoStore<U>) {\n this.store = store;\n }\n\n /** @deprecated use findById instead */\n findConnected(connected: string): Promise<U | undefined> {\n return this.store.findByKey(connected);\n }\n\n findById(userId: string): Promise<U | undefined> {\n return this.store.findByKey(userId);\n }\n\n insertOne(user: MongoInsertType<U>): Promise<any> {\n return this.store.insertOne(user);\n }\n\n replaceOne(user: U): Promise<any> {\n return this.store.replaceOne(user);\n }\n\n sanitize(user: U): USanitized {\n return this.sanitizeBaseUser(user) as USanitized;\n }\n\n findOneByAccountOrEmails({\n accountId,\n emails,\n provider,\n }: {\n accountId: number | string;\n emails?: string[];\n provider: string;\n }): Promise<U | undefined> {\n let query: any = {\n \"accounts.provider\": provider,\n \"accounts.accountId\": accountId,\n };\n\n if (emails && emails.length > 0) {\n query = {\n $or: [\n query,\n {\n emails: { $in: emails },\n },\n ],\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n return this.store.findOne(query);\n }\n\n updateAccount(user: U, account: Account): Promise<U> {\n const accountIndex = user.accounts.indexOf(account);\n if (accountIndex === -1) {\n throw new Error(\"Invalid account\");\n }\n\n return this.store.partialUpdateOne(user, {\n $set: {\n [`accounts.${accountIndex}`]: account,\n },\n } as Update<U>);\n }\n\n sanitizeBaseUser(user: U): UserSanitized {\n return {\n _id: user._id,\n created: user.created,\n updated: user.updated,\n displayName: user.displayName,\n fullName: user.fullName,\n status: user.status,\n emails: user.emails,\n emailDomains: user.emailDomains,\n accounts: user.accounts.map((account: Account) => ({\n provider: account.provider,\n accountId: account.accountId,\n name: account.name,\n status: account.status,\n profile: account.profile,\n })),\n };\n }\n}\n","/* eslint-disable @typescript-eslint/no-unsafe-argument */\n/* eslint-disable @typescript-eslint/explicit-module-boundary-types */\nimport type { Tokens } from \"../authentification/types\";\nimport type { AccountService, FullName } from \"./types\";\n\nexport default class UserAccountGoogleService<ScopeKeys extends \"login\">\n implements AccountService<ScopeKeys>\n{\n scopeKeyToScope: Record<ScopeKeys, string>;\n\n constructor(scopeKeyToScope: Record<Exclude<\"login\", ScopeKeys>, string>) {\n this.scopeKeyToScope = {\n ...scopeKeyToScope,\n login: \"openid profile email\",\n };\n }\n\n providerKey = \"google\";\n\n getProfile(tokens: Tokens): Promise<any> {\n // eslint-disable-next-line n/no-unsupported-features/node-builtins\n return fetch(\n `https://www.googleapis.com/oauth2/v1/userinfo?access_token=${tokens.accessToken}`,\n ).then((response) => response.json());\n }\n\n getId(profile: any): any {\n return profile.id;\n }\n\n getAccountName(profile: any): string | null | undefined {\n return profile.email;\n }\n\n getEmails(profile: any): string[] {\n const emails: string[] = [];\n\n if (profile.email) {\n emails.push(profile.email);\n }\n\n return emails;\n }\n\n getDisplayName(profile: any): string | null | undefined {\n return profile.name;\n }\n\n getFullName(profile: any): FullName {\n return {\n givenName: profile.given_name,\n familyName: profile.family_name,\n };\n }\n\n getDefaultScope(newScope: string): string[] {\n return this.getScope(undefined, newScope);\n }\n\n getScope(oldScope: string[] | undefined, newScope: string): string[] {\n return !oldScope\n ? newScope.split(\" \")\n : [...oldScope, ...newScope.split(\" \")].filter(\n (item, i, ar) => ar.indexOf(item) === i,\n );\n }\n}\n","/* eslint-disable @typescript-eslint/explicit-module-boundary-types */\nimport type { Tokens } from \"../authentification/types\";\nimport type { AccountService, FullName } from \"./types\";\n\n// https://api.slack.com/methods/users.identity\n\nexport default class UserAccountSlackService<ScopeKeys extends \"login\">\n implements AccountService<ScopeKeys>\n{\n scopeKeyToScope: Record<ScopeKeys, string>;\n\n constructor(scopeKeyToScope: Record<Exclude<\"login\", ScopeKeys>, string>) {\n this.scopeKeyToScope = {\n ...scopeKeyToScope,\n login: \"identity.basic identity.email identity.avatar\",\n };\n }\n\n providerKey = \"google\";\n\n getProfile(tokens: Tokens): Promise<any> {\n // eslint-disable-next-line n/no-unsupported-features/node-builtins\n return fetch(\n `https://slack.com/api/users.identity?token=${tokens.accessToken}`,\n ).then((response) => response.json());\n }\n\n getId(profile: any): string | null {\n if (!profile?.team?.id || !profile.user?.id) {\n return null;\n }\n return `team:${profile.team.id as string};user:${\n profile.user.id as string\n }`;\n }\n\n getAccountName(profile: any): string | null | undefined {\n return profile.user.email;\n }\n\n getEmails(profile: any): string[] {\n return profile.user.email ? [profile.user.email] : [];\n }\n\n getDisplayName(profile: any): string | null | undefined {\n return profile.user.name;\n }\n\n getFullName(profile: any): FullName | null {\n return null;\n }\n\n getDefaultScope(newScope: string): string[] {\n return this.getScope(undefined, newScope);\n }\n\n getScope(oldScope: string[] | undefined, newScope: string): string[] {\n return !oldScope\n ? newScope.split(\" \")\n : [...oldScope, ...newScope.split(\" \")].filter(\n (item, i, ar) => ar.indexOf(item) === i,\n );\n }\n}\n","import type { NodeApplication } from \"alp-node\";\nimport { Logger } from \"nightingale-logger\";\nimport type MongoUsersManager from \"./MongoUsersManager\";\nimport type { User } from \"./types\";\nimport { getTokenFromRequest } from \"./utils/cookies\";\nimport { createFindLoggedInUser } from \"./utils/createFindLoggedInUser\";\n\nconst logger = new Logger(\"alp:auth\");\n\nexport const authSocketIO = <U extends User = User>(\n app: NodeApplication,\n usersManager: MongoUsersManager<U>,\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n io: any,\n jwtAudience?: string,\n): void => {\n const findLoggedInUser = createFindLoggedInUser(\n app.config.get<{ secretKey: string }>(\"authentication\").secretKey,\n usersManager,\n logger,\n );\n\n const users = new Map();\n io.users = users;\n\n io.use(async (socket: any, next: any) => {\n const handshakeData = socket.request;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const token = getTokenFromRequest(handshakeData);\n\n if (!token) return next();\n\n const [loggedInUserId, loggedInUser] = await findLoggedInUser(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n jwtAudience || handshakeData.headers[\"user-agent\"],\n token,\n );\n\n if (!loggedInUserId || !loggedInUser) return next();\n\n socket.user = loggedInUser;\n users.set(socket.client.id, loggedInUser);\n\n socket.on(\"disconnected\", () => users.delete(socket.client.id));\n\n await next();\n });\n};\n","import type { IncomingMessage } from \"node:http\";\nimport type { NodeConfig } from \"alp-node\";\nimport { Logger } from \"nightingale-logger\";\nimport type MongoUsersManager from \"./MongoUsersManager\";\nimport type { User } from \"./types\";\nimport { COOKIE_NAME_TOKEN, getTokenFromRequest } from \"./utils/cookies\";\nimport { createFindLoggedInUser } from \"./utils/createFindLoggedInUser\";\n\nconst logger = new Logger(\"alp:auth\");\n\nconst getTokenFromReq = (\n req: IncomingMessage & { cookies?: Record<string, string> },\n): string | undefined => {\n if (req.cookies) return req.cookies[COOKIE_NAME_TOKEN];\n return getTokenFromRequest(req);\n};\n\n/*\n * Not tested yet.\n * @internal\n */\nexport const createAuthApolloContext = <U extends User = User>(\n config: NodeConfig,\n usersManager: MongoUsersManager<U>,\n): any => {\n const findLoggedInUser = createFindLoggedInUser(\n config.get<{ secretKey: string }>(\"authentication\").secretKey,\n usersManager,\n logger,\n );\n\n return async ({ req, connection }: { req: any; connection: any }) => {\n if (connection?.loggedInUser) {\n return { user: connection.loggedInUser };\n }\n\n if (!req) return null;\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const token = getTokenFromReq(req);\n\n if (!token) return { user: undefined };\n\n const [, loggedInUser] = await findLoggedInUser(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n req.headers[\"user-agent\"],\n token,\n );\n\n return { user: loggedInUser };\n };\n};\n","import type { IncomingMessage } from \"node:http\";\nimport { promisify } from \"node:util\";\nimport type { Context, ContextState, NodeApplication } from \"alp-node\";\nimport jsonwebtoken from \"jsonwebtoken\";\nimport { Logger } from \"nightingale-logger\";\nimport type MongoUsersManager from \"./MongoUsersManager\";\nimport type {\n AuthController as AuthControllerType,\n AuthHooks,\n} from \"./createAuthController\";\nimport { createAuthController } from \"./createAuthController\";\nimport type { AuthRoutes as AuthRoutesType } from \"./createRoutes\";\nimport { createRoutes } from \"./createRoutes\";\nimport type { Strategies } from \"./services/authentification/AuthenticationService\";\nimport { AuthenticationService } from \"./services/authentification/AuthenticationService\";\nimport type { AllowedStrategyKeys } from \"./services/authentification/types\";\nimport UserAccountsService from \"./services/user/UserAccountsService\";\nimport type { AccountService } from \"./services/user/types\";\nimport type { User, UserSanitized } from \"./types\";\nimport {\n COOKIE_NAME_STATE,\n COOKIE_NAME_TOKEN,\n getTokenFromRequest,\n} from \"./utils/cookies\";\nimport { createFindLoggedInUser } from \"./utils/createFindLoggedInUser\";\n\nexport { default as MongoUsersManager } from \"./MongoUsersManager\";\nexport { default as UserAccountGoogleService } from \"./services/user/UserAccountGoogleService\";\nexport { default as UserAccountSlackService } from \"./services/user/UserAccountSlackService\";\nexport { authSocketIO } from \"./authSocketIO\";\nexport { createAuthApolloContext } from \"./authApolloContext\";\nexport { STATUSES } from \"./services/user/UserAccountsService\";\n\nexport type * from \"./types\";\n\ndeclare module \"alp-node\" {\n interface ContextState {\n loggedInUserId:\n | NonNullable<ContextState[\"loggedInUser\"]>[\"_id\"]\n | null\n | undefined;\n loggedInUser: User | null | undefined;\n }\n\n interface ContextSanitizedState {\n loggedInUserId:\n | NonNullable<ContextSanitizedState[\"loggedInUser\"]>[\"_id\"]\n | null\n | undefined;\n loggedInUser: UserSanitized | null | undefined;\n }\n\n interface BaseContext {\n setLoggedIn: (\n loggedInUserId: NonNullable<ContextState[\"loggedInUserId\"]>,\n loggedInUser: NonNullable<ContextState[\"loggedInUser\"]>,\n ) => Promise<void>;\n logout: () => void;\n }\n}\n\nconst logger = new Logger(\"alp:auth\");\n\nconst signPromisified: any = promisify(jsonwebtoken.sign);\n\nexport type AuthController = AuthControllerType;\nexport type AuthRoutes = AuthRoutesType;\nexport { AuthenticationService } from \"./services/authentification/AuthenticationService\";\n\nexport default function init<\n StrategyKeys extends AllowedStrategyKeys = \"google\",\n U extends User = User,\n USanitized extends UserSanitized = UserSanitized,\n>({\n homeRouterKey,\n usersManager,\n strategies,\n defaultStrategy,\n strategyToService,\n authHooks,\n jwtAudience,\n}: {\n homeRouterKey?: string;\n usersManager: MongoUsersManager<U, USanitized>;\n strategies: Strategies<StrategyKeys>;\n defaultStrategy?: StrategyKeys;\n strategyToService: Record<StrategyKeys, AccountService<any>>;\n authHooks?: AuthHooks<StrategyKeys>;\n jwtAudience?: string;\n}) {\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n return (app: NodeApplication) => {\n const userAccountsService = new UserAccountsService(\n usersManager,\n strategyToService,\n );\n\n const authenticationService = new AuthenticationService(\n app.config,\n strategies,\n userAccountsService,\n );\n\n const controller = createAuthController({\n usersManager,\n authenticationService,\n homeRouterKey,\n defaultStrategy,\n authHooks,\n });\n\n app.context.setLoggedIn = async function setLoggedIn(\n this: Context,\n loggedInUserId: NonNullable<ContextState[\"loggedInUser\"]>[\"_id\"],\n loggedInUser: NonNullable<ContextState[\"loggedInUser\"]>,\n ): Promise<void> {\n logger.debug(\"setLoggedIn\", { loggedInUser });\n if (!loggedInUserId) {\n throw new Error(\"Illegal value for setLoggedIn\");\n }\n\n this.state.loggedInUserId = loggedInUserId;\n this.state.loggedInUser = loggedInUser;\n\n const token = await signPromisified(\n { loggedInUserId, time: Date.now() },\n this.config\n .get<Map<string, unknown>>(\"authentication\")\n .get(\"secretKey\"),\n {\n algorithm: \"HS512\",\n audience: jwtAudience || this.request.headers[\"user-agent\"],\n expiresIn: \"30 days\",\n },\n );\n\n const calcExpiresTime = (): number => {\n const date = new Date();\n date.setDate(date.getDate() + 30);\n return date.getTime();\n };\n\n this.cookies.set(COOKIE_NAME_TOKEN, token, {\n httpOnly: true,\n secure: this.config.get(\"allowHttps\"),\n });\n\n this.cookies.set(\n COOKIE_NAME_STATE,\n JSON.stringify({ loggedInUserId, expiresIn: calcExpiresTime() }),\n {\n httpOnly: false,\n secure: this.config.get(\"allowHttps\"),\n },\n );\n };\n\n app.context.logout = function logout(this: Context): void {\n delete this.state.loggedInUserId;\n delete this.state.loggedInUser;\n this.cookies.set(COOKIE_NAME_TOKEN, \"\", { expires: new Date(1) });\n this.cookies.set(COOKIE_NAME_STATE, \"\", { expires: new Date(1) });\n };\n\n const findLoggedInUser = createFindLoggedInUser(\n app.config.get<{ secretKey: string }>(\"authentication\").secretKey,\n usersManager,\n logger,\n );\n\n return {\n routes: createRoutes(controller),\n findLoggedInUserFromRequest: (\n req: IncomingMessage,\n ): ReturnType<typeof findLoggedInUser> => {\n const token = getTokenFromRequest(req);\n return findLoggedInUser(\n jwtAudience || req.headers[\"user-agent\"],\n token,\n );\n },\n findLoggedInUser,\n middleware: async <T>(\n ctx: Context,\n next: () => Promise<T> | T,\n ): Promise<T> => {\n const token = ctx.cookies.get(COOKIE_NAME_TOKEN);\n const userAgent = ctx.request.headers[\"user-agent\"];\n logger.debug(\"middleware\", { token });\n\n const setState = (\n loggedInUserId: U[\"_id\"] | null | undefined,\n loggedInUser: U | null | undefined,\n ): void => {\n ctx.state.loggedInUserId = loggedInUserId;\n ctx.state.user = loggedInUser;\n ctx.sanitizedState.loggedInUserId = loggedInUserId;\n ctx.sanitizedState.loggedInUser =\n loggedInUser && usersManager.sanitize(loggedInUser);\n };\n\n const [loggedInUserId, loggedInUser] = await findLoggedInUser(\n jwtAudience || userAgent,\n token,\n );\n logger.debug(\"middleware\", { loggedInUserId });\n\n if (loggedInUserId == null || loggedInUser == null) {\n if (token) {\n ctx.cookies.set(COOKIE_NAME_TOKEN, \"\", { expires: new Date(1) });\n ctx.cookies.set(COOKIE_NAME_STATE, \"\", { expires: new Date(1) });\n }\n setState(null, null);\n return next();\n }\n\n setState(loggedInUserId, loggedInUser);\n return next();\n },\n };\n };\n}\n"],"names":["createAuthController","usersManager","authenticationService","homeRouterKey","defaultStrategy","authHooks","login","ctx","strategy","namedRouteParam","Error","params","paramsForLogin","redirectAuthUrl","addScope","state","loggedInUser","redirectTo","scopeKey","response","assert","accessResponse","afterLoginSuccess","afterScopeUpdate","keyPath","store","setLoggedIn","logout","createRoutes","controller","segment","add","defaultRoute","randomBytesPromisified","promisify","randomBytes","randomHex","size","buffer","toString","logger","Logger","AuthenticationService","EventEmitter","constructor","config","strategies","userAccountsService","generateAuthUrl","debug","strategyInstance","type","oauth2","authorizationCode","authorizeURL","getTokens","options","result","getToken","code","redirect_uri","redirectUri","tokens","token","accessToken","access_token","refreshToken","refresh_token","tokenType","token_type","expiresIn","expires_in","expireDate","d","Date","setTime","getTime","idToken","id_token","tokensParam","clientCredentials","createToken","refresh","host","get","request","urlGenerator","user","accountId","scope","getScope","cookies","set","JSON","stringify","isLoginAccess","maxAge","httpOnly","secure","access_type","redirect","isLoggedIn","hooks","errorParam","queryParam","notEmpty","isValid","throw","value","validParams","cookieName","cookie","expires","parsedCookie","parse","findOrCreateFromStrategy","account","update","refreshAccountTokens","tokenExpireDate","now","Promise","resolve","provider","then","updateAccount","STATUSES","VALIDATED","DELETED","UserAccountsService","strategyToService","userId","_id","service","newScope","scopeKeyToScope","accounts","find","join","subservice","profile","getProfile","getId","status","undefined","subservices","includes","push","replaceOne","emails","getEmails","findOneByAccountOrEmails","providerKey","info","Object","assign","displayName","getDisplayName","fullName","getFullName","name","getAccountName","userEmails","forEach","email","emailDomains","reduce","domains","split","Set","insertOne","COOKIE_NAME_TOKEN","COOKIE_NAME_STATE","getTokenFromRequest","req","headers","authorization","startsWith","slice","Cookies","verifyPromisified","jsonwebtoken","verify","createDecodeJWT","secretKey","jwtAudience","algorithms","audience","loggedInUserId","createFindLoggedInUser","decodeJwt","error","err","findById","MongoUsersManager","findConnected","connected","findByKey","sanitize","sanitizeBaseUser","query","length","$or","$in","findOne","accountIndex","indexOf","partialUpdateOne","$set","created","updated","map","UserAccountGoogleService","fetch","json","id","givenName","given_name","familyName","family_name","getDefaultScope","oldScope","filter","item","i","ar","UserAccountSlackService","team","authSocketIO","app","io","findLoggedInUser","users","Map","use","socket","next","handshakeData","client","on","delete","getTokenFromReq","createAuthApolloContext","connection","signPromisified","sign","init","context","time","algorithm","date","setDate","getDate","routes","findLoggedInUserFromRequest","middleware","userAgent","setState","sanitizedState"],"mappings":"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