@rytass/member-base-nestjs-module
Version:
Rytass Member System NestJS Base Module
267 lines (264 loc) • 9.44 kB
JavaScript
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 };