UNPKG

@rytass/member-base-nestjs-module

Version:

Rytass Member System NestJS Base Module

267 lines (264 loc) 9.44 kB
import { randomBytes } from 'node:crypto'; import { MEMBER_BASE_MODULE_OPTIONS, LOGIN_FAILED_BAN_THRESHOLD, RESET_PASSWORD_TOKEN_EXPIRATION, RESET_PASSWORD_TOKEN_SECRET, PROVIDE_MEMBER_ENTITY, ACCESS_TOKEN_SECRET, ACCESS_TOKEN_EXPIRATION, REFRESH_TOKEN_SECRET, REFRESH_TOKEN_EXPIRATION, ENABLE_GLOBAL_GUARD, CASBIN_ENFORCER, CASBIN_PERMISSION_DECORATOR, CASBIN_PERMISSION_CHECKER, PASSWORD_SHOULD_INCLUDE_UPPERCASE, PASSWORD_SHOULD_INCLUDE_LOWERCASE, PASSWORD_SHOULD_INCLUDE_DIGIT, PASSWORD_SHOULD_INCLUDE_SPECIAL_CHARACTER, PASSWORD_MIN_LENGTH, PASSWORD_POLICY_REGEXP, PASSWORD_HISTORY_LIMIT, PASSWORD_AGE_LIMIT_IN_DAYS, ONLY_RESET_REFRESH_TOKEN_EXPIRATION_BY_PASSWORD, FORCE_REJECT_LOGIN_ON_PASSWORD_EXPIRED, CUSTOMIZED_JWT_PAYLOAD, OAUTH2_PROVIDERS, OAUTH2_CLIENT_DEST_URL, COOKIE_MODE, ACCESS_TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME, LOGIN_FAILED_AUTO_UNLOCK_SECONDS } from '../typings/member-base.tokens.js'; import { newEnforcer, newModelFromString } from 'casbin'; import { CASBIN_MODEL } from './casbin-models/rbac-with-domains.js'; import { AllowActions } from '../decorators/action.decorator.js'; import { DEFAULT_CASBIN_DOMAIN } from './default-casbin-domain.js'; const getTypeORMAdapter = async ()=>{ const module = await import('typeorm-adapter'); return module.default.default; }; const OptionProviders = [ { provide: LOGIN_FAILED_BAN_THRESHOLD, useFactory: (options)=>options?.loginFailedBanThreshold ?? 5, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: RESET_PASSWORD_TOKEN_EXPIRATION, useFactory: (options)=>options?.resetPasswordTokenExpiration ?? 60 * 60 * 1, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: RESET_PASSWORD_TOKEN_SECRET, useFactory: (options)=>options?.resetPasswordTokenSecret ?? randomBytes(16).toString('hex'), inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: PROVIDE_MEMBER_ENTITY, useFactory: (options)=>options?.memberEntity ?? null, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: ACCESS_TOKEN_SECRET, useFactory: (options)=>options?.accessTokenSecret ?? randomBytes(16).toString('hex'), inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: ACCESS_TOKEN_EXPIRATION, useFactory: (options)=>options?.accessTokenExpiration ?? 60 * 15, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: REFRESH_TOKEN_SECRET, useFactory: (options)=>options?.refreshTokenSecret ?? randomBytes(16).toString('hex'), inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: REFRESH_TOKEN_EXPIRATION, useFactory: (options)=>options?.refreshTokenExpiration ?? 60 * 60 * 24 * 90, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: ENABLE_GLOBAL_GUARD, useFactory: (options)=>options?.enableGlobalGuard ?? true, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: CASBIN_ENFORCER, useFactory: async (options)=>{ if (!options?.casbinAdapterOptions) return null; const TypeORMAdapter = await getTypeORMAdapter(); const adapter = await TypeORMAdapter.newAdapter(options.casbinAdapterOptions); const enforcer = await newEnforcer(); const model = newModelFromString(options.casbinModelString || CASBIN_MODEL); await enforcer.initWithModelAndAdapter(model, adapter); await enforcer.loadPolicy(); return enforcer; }, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: CASBIN_PERMISSION_DECORATOR, useFactory: async (options)=>options?.casbinPermissionDecorator ?? AllowActions, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: CASBIN_PERMISSION_CHECKER, useFactory: async (options)=>{ if (options?.casbinPermissionChecker) { return options.casbinPermissionChecker; } const domainResolver = options?.casbinDomainResolver; if (!domainResolver) { return ({ enforcer, payload, actions })=>Promise.all(actions.map(([subject, action])=>enforcer.enforce(payload.id, payload.domain ?? DEFAULT_CASBIN_DOMAIN, subject, action))).then((results)=>results.some((result)=>result)); } return async ({ enforcer, payload, actions, context, request })=>{ const resolved = await domainResolver({ context, request, payload, actions }); const domains = Array.isArray(resolved) ? resolved : [ resolved ]; if (!domains.length) return { allowed: false }; const candidates = domains.flatMap((domain)=>actions.map((action)=>({ domain, action }))); const results = await Promise.all(candidates.map(({ domain, action })=>enforcer.enforce(payload.id, domain, action[0], action[1]))); const matchedIndex = results.findIndex((result)=>result); if (matchedIndex === -1) return { allowed: false }; return { allowed: true, matchedDomain: candidates[matchedIndex].domain, matchedAction: candidates[matchedIndex].action }; }; }, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: PASSWORD_SHOULD_INCLUDE_UPPERCASE, useFactory: (options)=>options?.passwordShouldIncludeUppercase ?? true, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: PASSWORD_SHOULD_INCLUDE_LOWERCASE, useFactory: (options)=>options?.passwordShouldIncludeLowercase ?? true, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: PASSWORD_SHOULD_INCLUDE_DIGIT, useFactory: (options)=>options?.passwordShouldIncludeDigit ?? true, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: PASSWORD_SHOULD_INCLUDE_SPECIAL_CHARACTER, useFactory: (options)=>options?.passwordShouldIncludeSpecialCharacters ?? false, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: PASSWORD_MIN_LENGTH, useFactory: (options)=>options?.passwordMinLength ?? 8, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: PASSWORD_POLICY_REGEXP, useFactory: (options)=>options?.passwordPolicyRegExp ?? undefined, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: PASSWORD_HISTORY_LIMIT, useFactory: (options)=>options?.passwordHistoryLimit ?? undefined, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: PASSWORD_AGE_LIMIT_IN_DAYS, useFactory: (options)=>options?.passwordAgeLimitInDays ?? undefined, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: ONLY_RESET_REFRESH_TOKEN_EXPIRATION_BY_PASSWORD, useFactory: (options)=>options?.onlyResetRefreshTokenExpirationByPassword ?? false, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: FORCE_REJECT_LOGIN_ON_PASSWORD_EXPIRED, useFactory: (options)=>options?.forceRejectLoginOnPasswordExpired ?? false, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: CUSTOMIZED_JWT_PAYLOAD, useFactory: (options)=>options?.customizedJwtPayload ?? ((member)=>({ id: member.id, account: member.account })), inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: OAUTH2_PROVIDERS, useFactory: (options)=>options?.oauth2Providers ?? [], inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: OAUTH2_CLIENT_DEST_URL, useFactory: (options)=>options?.oauth2ClientDestUrl ?? '/login', inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: COOKIE_MODE, useFactory: (options)=>options?.cookieMode ?? false, inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: ACCESS_TOKEN_COOKIE_NAME, useFactory: (_options)=>'access_token', inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: REFRESH_TOKEN_COOKIE_NAME, useFactory: (_options)=>'refresh_token', inject: [ MEMBER_BASE_MODULE_OPTIONS ] }, { provide: LOGIN_FAILED_AUTO_UNLOCK_SECONDS, useFactory: (options)=>options?.loginFailedAutoUnlockSeconds ?? null, inject: [ MEMBER_BASE_MODULE_OPTIONS ] } ]; export { OptionProviders };