@rytass/member-base-nestjs-module
Version:
Rytass Member System NestJS Base Module
1,296 lines (1,270 loc) • 75.8 kB
JavaScript
'use strict';
var common = require('@nestjs/common');
var typeorm$1 = require('@nestjs/typeorm');
var typeorm = require('typeorm');
var argon2 = require('argon2');
var jsonwebtoken = require('jsonwebtoken');
var luxon = require('luxon');
var core = require('@nestjs/core');
var node_crypto = require('node:crypto');
var casbin = require('casbin');
var axios = require('axios');
function _ts_decorate$b(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_metadata$9(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
const MemberLoginLogRepo = Symbol('MemberLoginLogRepo');
class MemberLoginLogEntity {
id;
memberId;
success;
ip;
createdAt;
member;
}
_ts_decorate$b([
typeorm.PrimaryGeneratedColumn('uuid'),
_ts_metadata$9("design:type", String)
], MemberLoginLogEntity.prototype, "id", void 0);
_ts_decorate$b([
typeorm.Column('uuid'),
typeorm.Index(),
_ts_metadata$9("design:type", String)
], MemberLoginLogEntity.prototype, "memberId", void 0);
_ts_decorate$b([
typeorm.Column('boolean', {
default: true
}),
_ts_metadata$9("design:type", Boolean)
], MemberLoginLogEntity.prototype, "success", void 0);
_ts_decorate$b([
typeorm.Column('cidr', {
nullable: true
}),
_ts_metadata$9("design:type", Object)
], MemberLoginLogEntity.prototype, "ip", void 0);
_ts_decorate$b([
typeorm.CreateDateColumn(),
_ts_metadata$9("design:type", typeof Date === "undefined" ? Object : Date)
], MemberLoginLogEntity.prototype, "createdAt", void 0);
_ts_decorate$b([
typeorm.ManyToOne(()=>BaseMemberEntity, (member)=>member.loginLogs),
typeorm.JoinColumn({
name: 'memberId',
referencedColumnName: 'id'
}),
_ts_metadata$9("design:type", typeof Relation === "undefined" ? Object : Relation)
], MemberLoginLogEntity.prototype, "member", void 0);
MemberLoginLogEntity = _ts_decorate$b([
typeorm.Entity('member_login_logs')
], MemberLoginLogEntity);
function _ts_decorate$a(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_metadata$8(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
const MemberPasswordHistoryRepo = Symbol('MemberPasswordHistoryRepo');
class MemberPasswordHistoryEntity {
id;
memberId;
password;
createdAt;
member;
}
_ts_decorate$a([
typeorm.PrimaryGeneratedColumn('uuid'),
_ts_metadata$8("design:type", String)
], MemberPasswordHistoryEntity.prototype, "id", void 0);
_ts_decorate$a([
typeorm.Column('uuid'),
typeorm.Index(),
_ts_metadata$8("design:type", String)
], MemberPasswordHistoryEntity.prototype, "memberId", void 0);
_ts_decorate$a([
typeorm.Column('varchar'),
_ts_metadata$8("design:type", String)
], MemberPasswordHistoryEntity.prototype, "password", void 0);
_ts_decorate$a([
typeorm.CreateDateColumn(),
_ts_metadata$8("design:type", typeof Date === "undefined" ? Object : Date)
], MemberPasswordHistoryEntity.prototype, "createdAt", void 0);
_ts_decorate$a([
typeorm.ManyToOne(()=>BaseMemberEntity, (member)=>member.passwordHistories),
typeorm.JoinColumn({
name: 'memberId',
referencedColumnName: 'id'
}),
_ts_metadata$8("design:type", typeof Relation === "undefined" ? Object : Relation)
], MemberPasswordHistoryEntity.prototype, "member", void 0);
MemberPasswordHistoryEntity = _ts_decorate$a([
typeorm.Entity('member_password_histories')
], MemberPasswordHistoryEntity);
function _ts_decorate$9(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_metadata$7(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
const MemberOAuthRecordRepo = Symbol('MemberOAuthRecordRepo');
class MemberOAuthRecordEntity {
memberId;
channel;
channelIdentifier;
member;
}
_ts_decorate$9([
typeorm.PrimaryColumn('uuid'),
typeorm.Index(),
_ts_metadata$7("design:type", String)
], MemberOAuthRecordEntity.prototype, "memberId", void 0);
_ts_decorate$9([
typeorm.PrimaryColumn('varchar'),
_ts_metadata$7("design:type", String)
], MemberOAuthRecordEntity.prototype, "channel", void 0);
_ts_decorate$9([
typeorm.Column('varchar'),
_ts_metadata$7("design:type", String)
], MemberOAuthRecordEntity.prototype, "channelIdentifier", void 0);
_ts_decorate$9([
typeorm.ManyToOne(()=>BaseMemberEntity, (member)=>member.oauthRecords),
typeorm.JoinColumn({
name: 'memberId',
referencedColumnName: 'id'
}),
_ts_metadata$7("design:type", typeof Relation === "undefined" ? Object : Relation)
], MemberOAuthRecordEntity.prototype, "member", void 0);
MemberOAuthRecordEntity = _ts_decorate$9([
typeorm.Entity('member_oauth_records')
], MemberOAuthRecordEntity);
function _ts_decorate$8(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_metadata$6(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
const BaseMemberRepo = Symbol('BaseMemberRepo');
class BaseMemberEntity {
id;
account;
password;
passwordChangedAt;
resetPasswordRequestedAt;
loginFailedCounter;
shouldUpdatePassword;
createdAt;
updatedAt;
deletedAt;
loginLogs;
passwordHistories;
oauthRecords;
}
_ts_decorate$8([
typeorm.PrimaryGeneratedColumn('uuid'),
_ts_metadata$6("design:type", String)
], BaseMemberEntity.prototype, "id", void 0);
_ts_decorate$8([
typeorm.Column('varchar'),
_ts_metadata$6("design:type", String)
], BaseMemberEntity.prototype, "account", void 0);
_ts_decorate$8([
typeorm.Column('varchar'),
_ts_metadata$6("design:type", String)
], BaseMemberEntity.prototype, "password", void 0);
_ts_decorate$8([
typeorm.Column('timestamptz', {
default: 'now()'
}),
_ts_metadata$6("design:type", typeof Date === "undefined" ? Object : Date)
], BaseMemberEntity.prototype, "passwordChangedAt", void 0);
_ts_decorate$8([
typeorm.Column('timestamptz', {
nullable: true
}),
_ts_metadata$6("design:type", Object)
], BaseMemberEntity.prototype, "resetPasswordRequestedAt", void 0);
_ts_decorate$8([
typeorm.Column('int2', {
default: 0
}),
_ts_metadata$6("design:type", Number)
], BaseMemberEntity.prototype, "loginFailedCounter", void 0);
_ts_decorate$8([
typeorm.Column('boolean', {
default: false,
comment: 'Member should update password before login'
}),
_ts_metadata$6("design:type", Boolean)
], BaseMemberEntity.prototype, "shouldUpdatePassword", void 0);
_ts_decorate$8([
typeorm.CreateDateColumn(),
_ts_metadata$6("design:type", typeof Date === "undefined" ? Object : Date)
], BaseMemberEntity.prototype, "createdAt", void 0);
_ts_decorate$8([
typeorm.UpdateDateColumn(),
_ts_metadata$6("design:type", typeof Date === "undefined" ? Object : Date)
], BaseMemberEntity.prototype, "updatedAt", void 0);
_ts_decorate$8([
typeorm.DeleteDateColumn(),
_ts_metadata$6("design:type", Object)
], BaseMemberEntity.prototype, "deletedAt", void 0);
_ts_decorate$8([
typeorm.OneToMany(()=>MemberLoginLogEntity, (log)=>log.member),
_ts_metadata$6("design:type", typeof Relation === "undefined" ? Object : Relation)
], BaseMemberEntity.prototype, "loginLogs", void 0);
_ts_decorate$8([
typeorm.OneToMany(()=>MemberPasswordHistoryEntity, (history)=>history.member),
_ts_metadata$6("design:type", typeof Relation === "undefined" ? Object : Relation)
], BaseMemberEntity.prototype, "passwordHistories", void 0);
_ts_decorate$8([
typeorm.OneToMany(()=>MemberOAuthRecordEntity, (record)=>record.member),
_ts_metadata$6("design:type", typeof Relation === "undefined" ? Object : Relation)
], BaseMemberEntity.prototype, "oauthRecords", void 0);
BaseMemberEntity = _ts_decorate$8([
typeorm.Entity('members'),
typeorm.TableInheritance({
column: {
type: 'varchar',
name: 'entityName'
}
}),
typeorm.Index([
'account'
], {
unique: true,
where: '"deletedAt" IS NULL'
})
], BaseMemberEntity);
function _ts_decorate$7(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
const models = [
[
BaseMemberRepo,
BaseMemberEntity
],
[
MemberLoginLogRepo,
MemberLoginLogEntity
],
[
MemberPasswordHistoryRepo,
MemberPasswordHistoryEntity
],
[
MemberOAuthRecordRepo,
MemberOAuthRecordEntity
]
];
class MemberBaseModelsModule {
}
MemberBaseModelsModule = _ts_decorate$7([
common.Module({
imports: [
typeorm$1.TypeOrmModule.forFeature(models.map((model)=>model[1]))
],
providers: models.map(([symbol, entity])=>({
provide: symbol,
useFactory: (dataSource)=>dataSource.getRepository(entity),
inject: [
typeorm.DataSource
]
})),
exports: models.map((model)=>model[0])
})
], MemberBaseModelsModule);
// Typed injection tokens for better type safety (using Symbol with type annotations)
const MEMBER_BASE_MODULE_OPTIONS = Symbol('MEMBER_BASE_MODULE_OPTIONS');
const LOGIN_FAILED_BAN_THRESHOLD = Symbol('LOGIN_FAILED_BAN_THRESHOLD');
const RESET_PASSWORD_TOKEN_EXPIRATION = Symbol('RESET_PASSWORD_TOKEN_EXPIRATION');
const RESET_PASSWORD_TOKEN_SECRET = Symbol('RESET_PASSWORD_TOKEN_SECRET');
const CASBIN_ENFORCER = Symbol('CASBIN_ENFORCER');
const CASBIN_PERMISSION_DECORATOR = Symbol('CASBIN_PERMISSION_DECORATOR');
const CASBIN_PERMISSION_CHECKER = Symbol('CASBIN_PERMISSION_CHECKER');
const ACCESS_TOKEN_SECRET = Symbol('ACCESS_TOKEN_SECRET');
const ACCESS_TOKEN_EXPIRATION = Symbol('ACCESS_TOKEN_EXPIRATION');
const REFRESH_TOKEN_SECRET = Symbol('REFRESH_TOKEN_SECRET');
const REFRESH_TOKEN_EXPIRATION = Symbol('REFRESH_TOKEN_EXPIRATION');
const ENABLE_GLOBAL_GUARD = Symbol('ENABLE_GLOBAL_GUARD');
const ONLY_RESET_REFRESH_TOKEN_EXPIRATION_BY_PASSWORD = Symbol('ONLY_RESET_REFRESH_TOKEN_EXPIRATION_BY_PASSWORD');
const COOKIE_MODE = Symbol('COOKIE_MODE');
const LOGIN_FAILED_AUTO_UNLOCK_SECONDS = Symbol('LOGIN_FAILED_AUTO_UNLOCK_SECONDS');
const ACCESS_TOKEN_COOKIE_NAME = Symbol('ACCESS_TOKEN_COOKIE_NAME');
const REFRESH_TOKEN_COOKIE_NAME = Symbol('REFRESH_TOKEN_COOKIE_NAME');
// Options Entity Providers
const PROVIDE_MEMBER_ENTITY = Symbol('PROVIDE_MEMBER_ENTITY');
// Resolved Entity Repository Providers
const RESOLVED_MEMBER_REPO = Symbol('RESOLVED_MEMBER_REPO');
// Password Policy Providers
const PASSWORD_SHOULD_INCLUDE_UPPERCASE = Symbol('PASSWORD_SHOULD_INCLUDE_UPPERCASE');
const PASSWORD_SHOULD_INCLUDE_LOWERCASE = Symbol('PASSWORD_SHOULD_INCLUDE_LOWERCASE');
const PASSWORD_SHOULD_INCLUDE_DIGIT = Symbol('PASSWORD_SHOULD_INCLUDE_DIGIT');
const PASSWORD_SHOULD_INCLUDE_SPECIAL_CHARACTER = Symbol('PASSWORD_SHOULD_INCLUDE_SPECIAL_CHARACTER');
const PASSWORD_MIN_LENGTH = Symbol('PASSWORD_MIN_LENGTH');
const PASSWORD_POLICY_REGEXP = Symbol('PASSWORD_POLICY_REGEXP');
const PASSWORD_HISTORY_LIMIT = Symbol('PASSWORD_HISTORY_LIMIT');
const PASSWORD_AGE_LIMIT_IN_DAYS = Symbol('PASSWORD_AGE_LIMIT_IN_DAYS');
const FORCE_REJECT_LOGIN_ON_PASSWORD_EXPIRED = Symbol('FORCE_REJECT_LOGIN_ON_PASSWORD_EXPIRED');
const CUSTOMIZED_JWT_PAYLOAD = Symbol('CUSTOMIZED_JWT_PAYLOAD');
// OAuth2 Providers
const OAUTH2_PROVIDERS = Symbol('OAUTH2_PROVIDERS');
const OAUTH2_CLIENT_DEST_URL = Symbol('OAUTH2_CLIENT_DEST_URL');
class MemberNotFoundError extends common.BadRequestException {
constructor(){
super('Member not found');
}
code = 100;
}
class PasswordDoesNotMeetPolicyError extends common.BadRequestException {
constructor(){
super('Password does not meet the policy');
}
code = 101;
}
class InvalidPasswordError extends common.BadRequestException {
constructor(){
super('Invalid password');
}
code = 102;
}
class PasswordValidationError extends common.InternalServerErrorException {
constructor(){
super('Password validation error');
}
code = 103;
}
class InvalidToken extends common.BadRequestException {
constructor(){
super('Invalid token');
}
code = 104;
}
class MemberAlreadyExistedError extends common.BadRequestException {
constructor(){
super('Member already existed');
}
code = 105;
}
class PasswordChangedError extends common.BadRequestException {
constructor(){
super('Password changed, please sign in again');
}
code = 106;
}
class MemberBannedError extends common.BadRequestException {
constructor(){
super('Member banned');
}
code = 107;
}
class PasswordExpiredError extends common.BadRequestException {
constructor(){
super('Password expired, please update password');
}
code = 108;
}
class PasswordShouldUpdatePasswordError extends common.BadRequestException {
constructor(){
super('Member should update password before login');
}
code = 109;
}
class PasswordInHistoryError extends common.BadRequestException {
constructor(){
super('Password is in history');
}
code = 110;
}
function _ts_decorate$6(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_metadata$5(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
function _ts_param$5(paramIndex, decorator) {
return function(target, key) {
decorator(target, key, paramIndex);
};
}
// Use runtime require to avoid depending on @types/generate-password at consumers
const { generate } = require('generate-password');
class PasswordValidatorService {
passwordShouldIncludeUppercase;
passwordShouldIncludeLowercase;
passwordShouldIncludeDigit;
passwordShouldIncludeSpecialCharacter;
passwordMinLength;
passwordPolicyRegExp;
passwordHistoryLimit;
memberPasswordHistoryRepo;
passwordAgeLimitInDays;
constructor(passwordShouldIncludeUppercase, passwordShouldIncludeLowercase, passwordShouldIncludeDigit, passwordShouldIncludeSpecialCharacter, passwordMinLength, passwordPolicyRegExp, passwordHistoryLimit, memberPasswordHistoryRepo, passwordAgeLimitInDays){
this.passwordShouldIncludeUppercase = passwordShouldIncludeUppercase;
this.passwordShouldIncludeLowercase = passwordShouldIncludeLowercase;
this.passwordShouldIncludeDigit = passwordShouldIncludeDigit;
this.passwordShouldIncludeSpecialCharacter = passwordShouldIncludeSpecialCharacter;
this.passwordMinLength = passwordMinLength;
this.passwordPolicyRegExp = passwordPolicyRegExp;
this.passwordHistoryLimit = passwordHistoryLimit;
this.memberPasswordHistoryRepo = memberPasswordHistoryRepo;
this.passwordAgeLimitInDays = passwordAgeLimitInDays;
}
generateValidPassword() {
if (!this.passwordShouldIncludeDigit && !this.passwordShouldIncludeLowercase && !this.passwordShouldIncludeSpecialCharacter && !this.passwordShouldIncludeUppercase) {
// No specific policy, just generate a simple password
return generate({
length: this.passwordMinLength,
lowercase: true
});
}
return generate({
length: this.passwordMinLength,
numbers: this.passwordShouldIncludeDigit,
uppercase: this.passwordShouldIncludeUppercase,
lowercase: this.passwordShouldIncludeLowercase,
symbols: this.passwordShouldIncludeSpecialCharacter
});
}
shouldUpdatePassword(member) {
if (!this.passwordAgeLimitInDays) return false;
const validBefore = luxon.DateTime.fromJSDate(member.passwordChangedAt).plus({
days: this.passwordAgeLimitInDays
}).endOf('day').toMillis();
return validBefore < luxon.DateTime.now().toMillis();
}
async validatePassword(password, memberId) {
const trimmed = password.trim();
if (this.passwordPolicyRegExp) {
return this.passwordPolicyRegExp.test(trimmed);
}
if (trimmed.length < this.passwordMinLength) {
return false;
}
if (this.passwordShouldIncludeUppercase && !/[A-Z]/.test(trimmed)) {
return false;
}
if (this.passwordShouldIncludeLowercase && !/[a-z]/.test(trimmed)) {
return false;
}
if (this.passwordShouldIncludeDigit && !/[0-9]/.test(trimmed)) {
return false;
}
if (this.passwordShouldIncludeSpecialCharacter && !/[!><.,?@#$%^&*;:'"|\\/~`()-_+={}[\]]/.test(trimmed)) {
return false;
}
if (this.passwordHistoryLimit) {
const qb = this.memberPasswordHistoryRepo.createQueryBuilder('histories');
qb.andWhere('histories.memberId = :memberId', {
memberId
});
qb.addOrderBy('histories.createdAt', 'DESC');
qb.take(this.passwordHistoryLimit);
const histories = await qb.getMany();
try {
await histories.map((history)=>async ()=>{
const isVerify = await argon2.verify(history.password, password);
if (isVerify) {
throw new PasswordInHistoryError();
}
}).reduce((prev, next)=>prev.then(next), Promise.resolve());
} catch (_ex) {
return false;
}
}
return true;
}
}
PasswordValidatorService = _ts_decorate$6([
common.Injectable(),
_ts_param$5(0, common.Inject(PASSWORD_SHOULD_INCLUDE_UPPERCASE)),
_ts_param$5(1, common.Inject(PASSWORD_SHOULD_INCLUDE_LOWERCASE)),
_ts_param$5(2, common.Inject(PASSWORD_SHOULD_INCLUDE_DIGIT)),
_ts_param$5(3, common.Inject(PASSWORD_SHOULD_INCLUDE_SPECIAL_CHARACTER)),
_ts_param$5(4, common.Inject(PASSWORD_MIN_LENGTH)),
_ts_param$5(5, common.Inject(PASSWORD_POLICY_REGEXP)),
_ts_param$5(6, common.Inject(PASSWORD_HISTORY_LIMIT)),
_ts_param$5(7, common.Inject(MemberPasswordHistoryRepo)),
_ts_param$5(8, common.Inject(PASSWORD_AGE_LIMIT_IN_DAYS)),
_ts_metadata$5("design:type", Function),
_ts_metadata$5("design:paramtypes", [
Boolean,
Boolean,
Boolean,
Boolean,
Number,
Object,
Object,
typeof typeorm.Repository === "undefined" ? Object : typeorm.Repository,
Object
])
], PasswordValidatorService);
function _ts_decorate$5(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_metadata$4(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
function _ts_param$4(paramIndex, decorator) {
return function(target, key) {
decorator(target, key, paramIndex);
};
}
class MemberBaseService {
originalProvidedOptions;
baseMemberRepo;
memberLoginLogRepo;
loginFailedBanThreshold;
resetPasswordTokenExpiration;
resetPasswordTokenSecret;
accessTokenSecret;
accessTokenExpiration;
refreshTokenSecret;
refreshTokenExpiration;
onlyResetRefreshTokenExpirationByPassword;
memberPasswordHistoryRepo;
passwordAgeLimitInDays;
forceRejectLoginOnPasswordExpired;
passwordValidatorService;
customizedJwtPayload;
loginFailedAutoUnlockSeconds;
constructor(originalProvidedOptions, baseMemberRepo, memberLoginLogRepo, loginFailedBanThreshold, resetPasswordTokenExpiration, resetPasswordTokenSecret, accessTokenSecret, accessTokenExpiration, refreshTokenSecret, refreshTokenExpiration, onlyResetRefreshTokenExpirationByPassword, memberPasswordHistoryRepo, passwordAgeLimitInDays, forceRejectLoginOnPasswordExpired, passwordValidatorService, customizedJwtPayload, loginFailedAutoUnlockSeconds){
this.originalProvidedOptions = originalProvidedOptions;
this.baseMemberRepo = baseMemberRepo;
this.memberLoginLogRepo = memberLoginLogRepo;
this.loginFailedBanThreshold = loginFailedBanThreshold;
this.resetPasswordTokenExpiration = resetPasswordTokenExpiration;
this.resetPasswordTokenSecret = resetPasswordTokenSecret;
this.accessTokenSecret = accessTokenSecret;
this.accessTokenExpiration = accessTokenExpiration;
this.refreshTokenSecret = refreshTokenSecret;
this.refreshTokenExpiration = refreshTokenExpiration;
this.onlyResetRefreshTokenExpirationByPassword = onlyResetRefreshTokenExpirationByPassword;
this.memberPasswordHistoryRepo = memberPasswordHistoryRepo;
this.passwordAgeLimitInDays = passwordAgeLimitInDays;
this.forceRejectLoginOnPasswordExpired = forceRejectLoginOnPasswordExpired;
this.passwordValidatorService = passwordValidatorService;
this.customizedJwtPayload = customizedJwtPayload;
this.loginFailedAutoUnlockSeconds = loginFailedAutoUnlockSeconds;
}
logger = new common.Logger(MemberBaseService.name);
onApplicationBootstrap() {
if (!this.originalProvidedOptions?.accessTokenSecret || !this.originalProvidedOptions?.refreshTokenSecret) {
this.logger.warn('No access token secret or refresh token secret provided, using random secret');
}
}
signRefreshToken(member, domain) {
return jsonwebtoken.sign({
...this.customizedJwtPayload(member),
passwordChangedAt: member.passwordChangedAt?.getTime() ?? null,
...domain ? {
domain
} : {}
}, this.refreshTokenSecret, {
expiresIn: this.validateExpiration(this.refreshTokenExpiration, 'REFRESH_TOKEN_EXPIRATION')
});
}
signAccessToken(member, domain) {
return jsonwebtoken.sign({
...this.customizedJwtPayload(member),
...domain ? {
domain
} : {}
}, this.accessTokenSecret, {
expiresIn: this.validateExpiration(this.accessTokenExpiration, 'ACCESS_TOKEN_EXPIRATION')
});
}
async getResetPasswordToken(account) {
const member = await this.baseMemberRepo.findOne({
where: {
account
}
});
if (!member) {
throw new MemberNotFoundError();
}
const requestedOn = new Date();
member.resetPasswordRequestedAt = requestedOn;
await this.baseMemberRepo.save(member);
const token = jsonwebtoken.sign({
id: member.id,
requestedOn: requestedOn.getTime()
}, this.resetPasswordTokenSecret, {
expiresIn: this.validateExpiration(this.resetPasswordTokenExpiration, 'RESET_PASSWORD_TOKEN_EXPIRATION')
});
return token;
}
async changePassword(id, originPassword, newPassword) {
if (!await this.passwordValidatorService.validatePassword(newPassword, id)) {
throw new PasswordDoesNotMeetPolicyError();
}
const member = await this.baseMemberRepo.findOne({
where: {
id
}
});
if (!member) {
throw new MemberNotFoundError();
}
try {
if (await argon2.verify(member.password, originPassword)) {
member.password = await argon2.hash(newPassword);
member.passwordChangedAt = new Date();
member.shouldUpdatePassword = false;
await this.baseMemberRepo.save(member);
await this.memberPasswordHistoryRepo.save(this.memberPasswordHistoryRepo.create({
memberId: member.id,
password: member.password
}));
return member;
} else {
throw new InvalidPasswordError();
}
} catch (_err) {
throw new PasswordValidationError();
}
}
async changePasswordWithToken(token, newPassword) {
try {
const { id, requestedOn } = jsonwebtoken.verify(token, this.resetPasswordTokenSecret);
if (!await this.passwordValidatorService.validatePassword(newPassword, id)) {
throw new PasswordDoesNotMeetPolicyError();
}
const member = await this.baseMemberRepo.findOne({
where: {
id,
resetPasswordRequestedAt: new Date(requestedOn)
}
});
if (!member) {
throw new InvalidToken();
}
member.password = await argon2.hash(newPassword);
member.passwordChangedAt = new Date();
member.shouldUpdatePassword = false;
member.resetPasswordRequestedAt = null;
await this.baseMemberRepo.save(member);
await this.memberPasswordHistoryRepo.save(this.memberPasswordHistoryRepo.create({
memberId: member.id,
password: member.password
}));
return member;
} catch (_ex) {
throw new InvalidToken();
}
}
async register(account, password, memberOptions) {
if (!await this.passwordValidatorService.validatePassword(password)) {
throw new PasswordDoesNotMeetPolicyError();
}
let member = this.baseMemberRepo.create({
account
});
member.password = await argon2.hash(password);
try {
member = await this.baseMemberRepo.save({
...memberOptions,
account: member.account,
password: member.password
});
await this.memberPasswordHistoryRepo.save(this.memberPasswordHistoryRepo.create({
memberId: member.id,
password: member.password
}));
} catch (ex) {
if (/unique/.test(ex.message)) {
throw new MemberAlreadyExistedError();
}
}
return member;
}
async registerWithoutPassword(account, memberOptions) {
const password = this.passwordValidatorService.generateValidPassword();
let member = this.baseMemberRepo.create({
account
});
member.password = await argon2.hash(password);
try {
member = await this.baseMemberRepo.save({
shouldUpdatePassword: true,
...memberOptions,
account: member.account,
password: member.password
});
await this.memberPasswordHistoryRepo.save(this.memberPasswordHistoryRepo.create({
memberId: member.id,
password: member.password
}));
} catch (ex) {
if (/unique/.test(ex.message)) {
throw new MemberAlreadyExistedError();
}
}
return [
member,
password
];
}
async refreshToken(refreshToken, options) {
try {
const { id, account, passwordChangedAt, exp: _exp, domain } = jsonwebtoken.verify(refreshToken, this.refreshTokenSecret);
const member = await this.baseMemberRepo.findOne({
where: {
id,
account
}
});
if (!member) {
throw new MemberNotFoundError();
}
if (member.passwordChangedAt?.getTime() !== passwordChangedAt) {
throw new PasswordChangedError();
}
return {
accessToken: this.signAccessToken(member, options?.domain ?? domain ?? undefined),
refreshToken: this.signRefreshToken(member, options?.domain ?? domain ?? undefined)
};
} catch (ex) {
if (ex instanceof common.BadRequestException) throw ex;
throw new InvalidToken();
}
}
async login(account, password, options) {
const member = await this.baseMemberRepo.findOne({
where: {
account
}
});
if (!member) {
throw new MemberNotFoundError();
}
if (member.loginFailedCounter >= this.loginFailedBanThreshold) {
if (this.loginFailedAutoUnlockSeconds) {
const latestFailedRecord = await this.memberLoginLogRepo.findOne({
order: {
createdAt: 'DESC'
},
where: {
memberId: member.id,
success: false
}
});
if (!latestFailedRecord || latestFailedRecord.createdAt.getTime() + this.loginFailedAutoUnlockSeconds * 1000 > Date.now()) {
throw new MemberBannedError();
}
} else {
throw new MemberBannedError();
}
}
const isPasswordExpired = this.passwordAgeLimitInDays ? this.passwordValidatorService.shouldUpdatePassword(member) : false;
if (isPasswordExpired && this.forceRejectLoginOnPasswordExpired) {
throw new PasswordExpiredError();
}
const ip = typeof options === 'string' ? options : options?.ip ?? undefined;
try {
if (await argon2.verify(member.password, password)) {
if (member.shouldUpdatePassword) {
throw new PasswordShouldUpdatePasswordError();
}
member.loginFailedCounter = 0;
await this.baseMemberRepo.save(member);
// async log
this.memberLoginLogRepo.save({
memberId: member.id,
success: true,
ip: ip ? `${ip}/32` : null
});
const domain = typeof options === 'string' ? undefined : options?.domain ?? undefined;
return {
accessToken: this.signAccessToken(member, domain),
refreshToken: this.signRefreshToken(member, domain),
...this.passwordAgeLimitInDays ? {
shouldUpdatePassword: isPasswordExpired,
passwordChangedAt: member.passwordChangedAt?.toISOString()
} : {}
};
}
member.loginFailedCounter += 1;
await this.baseMemberRepo.save(member);
// async log
this.memberLoginLogRepo.save({
memberId: member.id,
success: false,
ip: ip ? `${ip}/32` : null
});
throw new InvalidPasswordError();
} catch (err) {
if (err instanceof common.BadRequestException) throw err;
throw new PasswordValidationError();
}
}
async resetLoginFailedCounter(id) {
const member = await this.baseMemberRepo.findOne({
where: {
id
}
});
if (!member) {
throw new MemberNotFoundError();
}
member.loginFailedCounter = 0;
await this.baseMemberRepo.save(member);
return member;
}
validateExpiration(source, tokenName) {
if (typeof source !== 'number' || Number.isNaN(source)) {
throw new common.BadRequestException(`[${tokenName}] must be a number (in seconds), but got: ${source}`);
}
return source;
}
}
MemberBaseService = _ts_decorate$5([
common.Injectable(),
_ts_param$4(0, common.Inject(MEMBER_BASE_MODULE_OPTIONS)),
_ts_param$4(1, common.Inject(RESOLVED_MEMBER_REPO)),
_ts_param$4(2, common.Inject(MemberLoginLogRepo)),
_ts_param$4(3, common.Inject(LOGIN_FAILED_BAN_THRESHOLD)),
_ts_param$4(4, common.Inject(RESET_PASSWORD_TOKEN_EXPIRATION)),
_ts_param$4(5, common.Inject(RESET_PASSWORD_TOKEN_SECRET)),
_ts_param$4(6, common.Inject(ACCESS_TOKEN_SECRET)),
_ts_param$4(7, common.Inject(ACCESS_TOKEN_EXPIRATION)),
_ts_param$4(8, common.Inject(REFRESH_TOKEN_SECRET)),
_ts_param$4(9, common.Inject(REFRESH_TOKEN_EXPIRATION)),
_ts_param$4(10, common.Inject(ONLY_RESET_REFRESH_TOKEN_EXPIRATION_BY_PASSWORD)),
_ts_param$4(11, common.Inject(MemberPasswordHistoryRepo)),
_ts_param$4(12, common.Inject(PASSWORD_AGE_LIMIT_IN_DAYS)),
_ts_param$4(13, common.Inject(FORCE_REJECT_LOGIN_ON_PASSWORD_EXPIRED)),
_ts_param$4(14, common.Inject(PasswordValidatorService)),
_ts_param$4(15, common.Inject(CUSTOMIZED_JWT_PAYLOAD)),
_ts_param$4(16, common.Inject(LOGIN_FAILED_AUTO_UNLOCK_SECONDS)),
_ts_metadata$4("design:type", Function),
_ts_metadata$4("design:paramtypes", [
Object,
typeof typeorm.Repository === "undefined" ? Object : typeorm.Repository,
typeof typeorm.Repository === "undefined" ? Object : typeorm.Repository,
Number,
Number,
String,
String,
Number,
String,
Number,
Boolean,
typeof typeorm.Repository === "undefined" ? Object : typeorm.Repository,
Object,
Boolean,
typeof PasswordValidatorService === "undefined" ? Object : PasswordValidatorService,
Function,
Object
])
], MemberBaseService);
function _ts_decorate$4(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_metadata$3(k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
function _ts_param$3(paramIndex, decorator) {
return function(target, key) {
decorator(target, key, paramIndex);
};
}
class MemberBaseAdminService {
baseMemberRepo;
passwordValidatorService;
memberPasswordHistoryRepo;
constructor(baseMemberRepo, passwordValidatorService, memberPasswordHistoryRepo){
this.baseMemberRepo = baseMemberRepo;
this.passwordValidatorService = passwordValidatorService;
this.memberPasswordHistoryRepo = memberPasswordHistoryRepo;
}
async archiveMember(id) {
const member = await this.baseMemberRepo.findOne({
where: {
id
}
});
if (!member) {
throw new MemberNotFoundError();
}
await this.baseMemberRepo.softRemove(member);
}
async resetMemberPassword(id, newPassword, ignorePasswordPolicy = false) {
if (!ignorePasswordPolicy && !await this.passwordValidatorService.validatePassword(newPassword, id)) {
throw new PasswordDoesNotMeetPolicyError();
}
const member = await this.baseMemberRepo.findOne({
where: {
id
}
});
if (!member) {
throw new MemberNotFoundError();
}
member.password = await argon2.hash(newPassword);
member.passwordChangedAt = new Date();
await this.baseMemberRepo.save(member);
await this.memberPasswordHistoryRepo.save(this.memberPasswordHistoryRepo.create({
memberId: member.id,
password: member.password
}));
return member;
}
}
MemberBaseAdminService = _ts_decorate$4([
common.Injectable(),
_ts_param$3(0, common.Inject(RESOLVED_MEMBER_REPO)),
_ts_param$3(1, common.Inject(PasswordValidatorService)),
_ts_param$3(2, common.Inject(MemberPasswordHistoryRepo)),
_ts_metadata$3("design:type", Function),
_ts_metadata$3("design:paramtypes", [
typeof typeorm.Repository === "undefined" ? Object : typeorm.Repository,
typeof PasswordValidatorService === "undefined" ? Object : PasswordValidatorService,
typeof typeorm.Repository === "undefined" ? Object : typeorm.Repository
])
], MemberBaseAdminService);
const TARGETS = [
[
BaseMemberRepo,
PROVIDE_MEMBER_ENTITY,
RESOLVED_MEMBER_REPO
]
];
const ResolvedRepoProviders = TARGETS.map(([repo, provide, resolved])=>({
provide: resolved,
useFactory: (baseRepo, entity, dataSource)=>entity ? dataSource.getRepository(entity) : baseRepo,
inject: [
repo,
provide,
typeorm.DataSource
]
}));
const CASBIN_MODEL = `
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
`;
const AllowActions = core.Reflector.createDecorator();
const DEFAULT_CASBIN_DOMAIN = '::DEFAULT::';
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 ?? node_crypto.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 ?? node_crypto.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 ?? node_crypto.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 casbin.newEnforcer();
const model = casbin.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
]
}
];
const IS_ROUTE_PUBLIC = 'IS_ROUTE_PUBLIC';
const IsPublic = ()=>common.SetMetadata(IS_ROUTE_PUBLIC, true);
const IS_ROUTE_ONLY_AUTHENTICATED = 'IS_ROUTE_ONLY_AUTHENTICATED';
const Authenticated = ()=>common.SetMetadata(IS_ROUTE_ONLY_AUTHENTICATED, true);
const getRequestFromContext = (context)=>{
const contextType = context.getType();
switch(contextType){
case 'graphql':
{
const { GqlExecutionContext } = require('@nestjs/graphql');
return GqlExecutionContext.create(context).getContext().req;
}
case 'http':
default:
return context.switchToHttp().getRequest();
}
};
const getTokenFromContext = async (context, cookieMode = false, cookieName = 'access_token')=>{
const request = await getRequestFromContext(context);
const headerToken = (request.headers.authorization ?? '').replace(/^Bearer\s/i, '').trim();
if (headerToken) return headerToken;
if (cookieMode) {
const cookieToken = request.cookies?.[cookieName];
if (cookieToken) return cookieToken;
}
return null;
};
const normalizeCasbinDecision = (result)=>typeof result === 'boolean' ? {
allowed: result
} : result;
function _ts_decorate$3(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorator