@nest-my-admin/auth 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nest-my-admin contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,11 @@
1
- # Temporary Holding Version
1
+ # @nest-my-admin/auth
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The built-in sign-in for [nest-my-admin](https://github.com/phantomboy0/nest-my-admin): admin users, hashed passwords, revocable sessions, lockout and two-factor sign-in.
4
+
5
+ ```ts
6
+ import { builtinAuth, ADMIN_AUTH_ENTITIES } from '@nest-my-admin/auth';
7
+
8
+ AdminModule.forRoot({ auth: builtinAuth({ bootstrapSuperuser: { username: 'admin', password: process.env.ADMIN_PASSWORD! } }) });
9
+ ```
10
+
11
+ Add `ADMIN_AUTH_ENTITIES` to your TypeORM entities. Documentation: https://github.com/phantomboy0/nest-my-admin#readme
@@ -0,0 +1,120 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import { type DataSource } from 'typeorm';
3
+ import { type AdminAuthAdapter, type AdminAuthConfig, type AdminPrincipal, type AdminSessionInfo, type AdminUser, type AdminUserChanges, type AdminUserRecord, type NewAdminUser, type AuthIO, type LoginInput, type TwoFactorStatus } from '@nest-my-admin/core';
4
+ import { NmaUser } from './entities.js';
5
+ import { SCRYPT, type PasswordPolicy } from './password.js';
6
+ export interface BuiltinAuthOptions {
7
+ /** The TypeORM DataSource holding `ADMIN_AUTH_ENTITIES` (its name; default the default DataSource). */
8
+ dataSource?: string;
9
+ /** Default `nma_session`. */
10
+ cookieName?: string;
11
+ /** `auto` (default): Secure on https requests (Express's `trust proxy` decides for proxied ones). */
12
+ secureCookie?: boolean | 'auto';
13
+ /** A session ends after this many seconds without a request. Default 12 hours. */
14
+ idleTimeout?: number;
15
+ /** …and at the latest this many seconds after sign-in. Default 30 days. */
16
+ absoluteTimeout?: number;
17
+ /** Failed passwords before the account locks, and for how long. Default 5 attempts, 15 minutes. */
18
+ lockout?: {
19
+ attempts?: number;
20
+ minutes?: number;
21
+ };
22
+ /** Sign-in attempts per IP address per minute. Default 20. */
23
+ loginAttemptsPerMinute?: number;
24
+ password?: PasswordPolicy;
25
+ /** Creates this superuser at boot when there are no admin users yet. */
26
+ bootstrapSuperuser?: {
27
+ username: string;
28
+ password: string;
29
+ displayName?: string;
30
+ email?: string;
31
+ };
32
+ /** Encrypts two-factor keys at rest (AES-256-GCM): 32 bytes, base64 (`openssl rand -base64 32`). Keep it out of the repository. */
33
+ secretKey?: string;
34
+ /** The name authenticator apps show next to the code. Default `nest-my-admin`. */
35
+ issuer?: string;
36
+ /** @internal Tests: the clock and a cheaper hash. */
37
+ now?: () => Date;
38
+ /** @internal */
39
+ scrypt?: typeof SCRYPT;
40
+ }
41
+ /** The built-in adapter (spec §7): `nma_user` and `nma_session` on the host's DataSource. */
42
+ export declare class BuiltinAuthAdapter implements AdminAuthAdapter {
43
+ private readonly dataSource;
44
+ private readonly users;
45
+ private readonly sessions;
46
+ private readonly options;
47
+ private readonly limiter;
48
+ private readonly box;
49
+ private readonly logger;
50
+ /** Checked for unknown usernames, so they cost as much time as a wrong password. */
51
+ private dummyHash?;
52
+ constructor(dataSource: DataSource, options?: BuiltinAuthOptions);
53
+ /** Creates the bootstrap superuser when the table is empty. */
54
+ init(bootstrap?: BuiltinAuthOptions['bootstrapSuperuser']): Promise<void>;
55
+ authenticate(req: IncomingMessage): Promise<AdminPrincipal | null>;
56
+ login(input: LoginInput, io: AuthIO): Promise<AdminPrincipal>;
57
+ private failed;
58
+ /**
59
+ * Records `step` as used, only if no sign-in used it (or a later one) meanwhile: the same code sent twice at once
60
+ * signs in once. Drivers that do not report affected rows fall back to the check before.
61
+ */
62
+ private claimStep;
63
+ private secretOf;
64
+ /** The stored hashes without the one `code` matches, or undefined when it matches none. */
65
+ private useRecoveryCode;
66
+ logout(principal: AdminPrincipal, io: AuthIO): Promise<void>;
67
+ listSessions(principal: AdminPrincipal): Promise<AdminSessionInfo[]>;
68
+ revokeSession(principal: AdminPrincipal, sessionId: string): Promise<void>;
69
+ revokeOtherSessions(principal: AdminPrincipal): Promise<void>;
70
+ changePassword(principal: AdminPrincipal, input: {
71
+ current: string;
72
+ next: string;
73
+ }): Promise<void>;
74
+ private userOf;
75
+ private checkPassword;
76
+ twoFactorStatus(principal: AdminPrincipal): Promise<TwoFactorStatus>;
77
+ beginTwoFactor(principal: AdminPrincipal): Promise<{
78
+ secret: string;
79
+ otpauthUrl: string;
80
+ }>;
81
+ confirmTwoFactor(principal: AdminPrincipal, code: string): Promise<{
82
+ recoveryCodes: string[];
83
+ }>;
84
+ disableTwoFactor(principal: AdminPrincipal, password: string): Promise<void>;
85
+ newRecoveryCodes(principal: AdminPrincipal, password: string): Promise<{
86
+ recoveryCodes: string[];
87
+ }>;
88
+ /** Turns off a user's second factor (the Users page) and ends their sessions. */
89
+ resetTwoFactor(id: string): Promise<void>;
90
+ private readonly warned;
91
+ private warnOnce;
92
+ listUsers(query: {
93
+ search?: string;
94
+ page: number;
95
+ pageSize: number;
96
+ }): Promise<{
97
+ items: AdminUserRecord[];
98
+ total: number;
99
+ }>;
100
+ getUser(id: string): Promise<AdminUserRecord | null>;
101
+ createUser(input: NewAdminUser): Promise<AdminUserRecord>;
102
+ updateUser(id: string, changes: AdminUserChanges): Promise<AdminUserRecord>;
103
+ /** Sets a new password (the policy applies) and ends every session of that user. */
104
+ setPassword(id: string, password: string): Promise<void>;
105
+ private setCookie;
106
+ }
107
+ export declare function toAdminUser(user: NmaUser): AdminUser;
108
+ /** Creates an admin user (a seed script, a CLI of your own). The password must meet the policy. */
109
+ export declare function createAdminUser(dataSource: DataSource, input: {
110
+ username: string;
111
+ password: string;
112
+ displayName?: string;
113
+ email?: string;
114
+ isSuperuser?: boolean;
115
+ }, options?: {
116
+ password?: PasswordPolicy;
117
+ scrypt?: typeof SCRYPT;
118
+ }): Promise<NmaUser>;
119
+ /** `AdminModule.forRoot({ auth: builtinAuth({ … }) })`. */
120
+ export declare function builtinAuth(options?: BuiltinAuthOptions): AdminAuthConfig;
@@ -0,0 +1,434 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { Logger } from '@nestjs/common';
3
+ import { getDataSourceToken } from '@nestjs/typeorm';
4
+ import { LessThan, Not } from 'typeorm';
5
+ import { AdminAuth, AdminRateLimitError, AdminTwoFactorRequiredError, AdminUnauthenticatedError, AdminValidationError, appendSetCookie, isSecureRequest, parseCookies, serializeCookie, } from '@nest-my-admin/core';
6
+ import { NmaSession, NmaUser } from './entities.js';
7
+ import { SCRYPT, hashPassword, passwordProblems, verifyPassword } from './password.js';
8
+ import { RateLimiter } from './rate-limit.js';
9
+ import { SecretBox } from './secret-box.js';
10
+ import { base32Decode, base32Encode, newTotpSecret, otpauthUrl, verifyTotp } from './totp.js';
11
+ const WRONG = 'Wrong username or password';
12
+ const WRONG_CODE = 'The code is wrong or was already used';
13
+ const RECOVERY_CODES = 10;
14
+ const sha256 = (value) => createHash('sha256').update(value).digest('hex');
15
+ /** The admin's mount path, from the request (Express sets `baseUrl` for the handler mounted at `path`). */
16
+ function cookiePath(req) {
17
+ const base = req.baseUrl;
18
+ return base && base.startsWith('/') ? base : '/';
19
+ }
20
+ function clientIp(req) {
21
+ return req.ip ?? req.socket?.remoteAddress ?? 'unknown';
22
+ }
23
+ /** The built-in adapter (spec §7): `nma_user` and `nma_session` on the host's DataSource. */
24
+ export class BuiltinAuthAdapter {
25
+ dataSource;
26
+ users;
27
+ sessions;
28
+ options;
29
+ limiter;
30
+ box;
31
+ logger = new Logger('NestMyAdmin');
32
+ /** Checked for unknown usernames, so they cost as much time as a wrong password. */
33
+ dummyHash;
34
+ constructor(dataSource, options = {}) {
35
+ this.dataSource = dataSource;
36
+ for (const entity of [NmaUser, NmaSession]) {
37
+ if (!dataSource.hasMetadata(entity)) {
38
+ throw new Error(`@nest-my-admin/auth: add ADMIN_AUTH_ENTITIES to the entities of the DataSource "${options.dataSource ?? 'default'}" (${entity.name} is missing)`);
39
+ }
40
+ }
41
+ this.users = dataSource.getRepository(NmaUser);
42
+ this.sessions = dataSource.getRepository(NmaSession);
43
+ this.options = {
44
+ cookieName: options.cookieName ?? 'nma_session',
45
+ secureCookie: options.secureCookie ?? 'auto',
46
+ idleTimeout: options.idleTimeout ?? 12 * 3600,
47
+ absoluteTimeout: options.absoluteTimeout ?? 30 * 24 * 3600,
48
+ lockoutAttempts: options.lockout?.attempts ?? 5,
49
+ lockoutMs: (options.lockout?.minutes ?? 15) * 60_000,
50
+ loginAttemptsPerMinute: options.loginAttemptsPerMinute ?? 20,
51
+ password: options.password ?? {},
52
+ now: options.now ?? (() => new Date()),
53
+ scrypt: options.scrypt ?? SCRYPT,
54
+ issuer: options.issuer ?? 'nest-my-admin',
55
+ };
56
+ this.box = new SecretBox(options.secretKey);
57
+ this.limiter = new RateLimiter(this.options.loginAttemptsPerMinute, 60_000);
58
+ }
59
+ /** Creates the bootstrap superuser when the table is empty. */
60
+ async init(bootstrap) {
61
+ if (bootstrap && (await this.users.count()) === 0) {
62
+ await createAdminUser(this.dataSource, { ...bootstrap, isSuperuser: true }, { password: this.options.password, scrypt: this.options.scrypt });
63
+ }
64
+ }
65
+ async authenticate(req) {
66
+ const token = parseCookies(req.headers.cookie)[this.options.cookieName];
67
+ if (!token || token.length > 200)
68
+ return null;
69
+ const session = await this.sessions.findOne({ where: { tokenHash: sha256(token) }, relations: { user: true } });
70
+ if (!session)
71
+ return null;
72
+ const now = this.options.now();
73
+ const idleLimit = session.lastSeenAt.getTime() + this.options.idleTimeout * 1000;
74
+ if (now.getTime() >= session.expiresAt.getTime() || now.getTime() >= idleLimit || !session.user?.isActive) {
75
+ await this.sessions.delete({ id: session.id });
76
+ return null;
77
+ }
78
+ // Sliding expiry, written at most once a minute.
79
+ if (now.getTime() - session.lastSeenAt.getTime() > 60_000)
80
+ await this.sessions.update({ id: session.id }, { lastSeenAt: now });
81
+ return { user: toAdminUser(session.user), csrfToken: session.csrfToken, sessionId: session.id };
82
+ }
83
+ async login(input, io) {
84
+ const now = this.options.now();
85
+ const limit = this.limiter.hit(clientIp(io.req), now.getTime());
86
+ if (!limit.allowed)
87
+ throw new AdminRateLimitError('Too many sign-in attempts; wait a minute and try again', limit.retryAfter);
88
+ const username = input.username.trim().toLowerCase();
89
+ const user = await this.users.findOne({ where: { username } });
90
+ // Unknown users are checked against a dummy hash: the same work, the same answer.
91
+ this.dummyHash ??= hashPassword(randomBytes(16).toString('hex'), this.options.scrypt);
92
+ const matches = await verifyPassword(input.password, user?.passwordHash ?? (await this.dummyHash));
93
+ const locked = user?.lockedUntil !== null && user?.lockedUntil !== undefined && user.lockedUntil.getTime() > now.getTime();
94
+ if (!user || !user.isActive || locked || !matches) {
95
+ if (user && !locked && !matches)
96
+ await this.failed(user, now);
97
+ throw new AdminUnauthenticatedError(WRONG);
98
+ }
99
+ // The second factor: no session without it. A wrong code counts toward the lockout like a wrong password.
100
+ const second = {};
101
+ if (user.totpSecret) {
102
+ if (input.otp === undefined)
103
+ throw new AdminTwoFactorRequiredError();
104
+ const step = verifyTotp(this.secretOf(user.totpSecret), input.otp, now.getTime(), { after: user.totpLastStep });
105
+ if (step === null || !(await this.claimStep(user, step))) {
106
+ const remaining = this.useRecoveryCode(user, input.otp);
107
+ if (!remaining) {
108
+ await this.failed(user, now);
109
+ throw new AdminTwoFactorRequiredError(WRONG_CODE);
110
+ }
111
+ second.recoveryCodes = remaining;
112
+ }
113
+ }
114
+ await this.users.update({ id: user.id }, { failedLogins: 0, lockedUntil: null, lastLoginAt: now, ...second });
115
+ // A sign-in clears the user's expired sessions.
116
+ await this.sessions.delete({ userId: user.id, expiresAt: LessThan(now) });
117
+ const token = randomBytes(32).toString('base64url');
118
+ const session = this.sessions.create({
119
+ id: randomBytes(16).toString('hex'),
120
+ tokenHash: sha256(token),
121
+ userId: user.id,
122
+ csrfToken: randomBytes(24).toString('base64url'),
123
+ createdAt: now,
124
+ lastSeenAt: now,
125
+ expiresAt: new Date(now.getTime() + this.options.absoluteTimeout * 1000),
126
+ userAgent: String(io.req.headers['user-agent'] ?? '').slice(0, 300) || null,
127
+ ip: clientIp(io.req).slice(0, 64),
128
+ });
129
+ await this.sessions.insert(session);
130
+ this.setCookie(io, token, this.options.absoluteTimeout);
131
+ return { user: toAdminUser(user), csrfToken: session.csrfToken, sessionId: session.id };
132
+ }
133
+ async failed(user, now) {
134
+ user.failedLogins += 1;
135
+ if (user.failedLogins >= this.options.lockoutAttempts) {
136
+ user.failedLogins = 0;
137
+ user.lockedUntil = new Date(now.getTime() + this.options.lockoutMs);
138
+ }
139
+ await this.users.update({ id: user.id }, { failedLogins: user.failedLogins, lockedUntil: user.lockedUntil });
140
+ }
141
+ /**
142
+ * Records `step` as used, only if no sign-in used it (or a later one) meanwhile: the same code sent twice at once
143
+ * signs in once. Drivers that do not report affected rows fall back to the check before.
144
+ */
145
+ async claimStep(user, step) {
146
+ const column = this.dataSource.driver.escape('totpLastStep');
147
+ const result = await this.users
148
+ .createQueryBuilder()
149
+ .update()
150
+ .set({ totpLastStep: step })
151
+ .where('id = :id', { id: user.id })
152
+ .andWhere(`(${column} IS NULL OR ${column} < :step)`, { step })
153
+ .execute();
154
+ return result.affected === undefined || result.affected > 0;
155
+ }
156
+ secretOf(stored) {
157
+ return base32Decode(this.box.open(stored));
158
+ }
159
+ /** The stored hashes without the one `code` matches, or undefined when it matches none. */
160
+ useRecoveryCode(user, code) {
161
+ const hashes = parseHashes(user.recoveryCodes);
162
+ const hash = sha256(normalizeRecoveryCode(code));
163
+ if (!hashes.includes(hash))
164
+ return undefined;
165
+ return JSON.stringify(hashes.filter((candidate) => candidate !== hash));
166
+ }
167
+ async logout(principal, io) {
168
+ if (principal.sessionId)
169
+ await this.sessions.delete({ id: principal.sessionId });
170
+ this.setCookie(io, '', 0);
171
+ }
172
+ async listSessions(principal) {
173
+ const now = this.options.now().getTime();
174
+ const rows = await this.sessions.find({ where: { userId: Number(principal.user.id) }, order: { lastSeenAt: 'DESC' } });
175
+ return rows
176
+ .filter((row) => row.expiresAt.getTime() > now && row.lastSeenAt.getTime() + this.options.idleTimeout * 1000 > now)
177
+ .map((row) => ({
178
+ id: row.id,
179
+ createdAt: row.createdAt,
180
+ lastSeenAt: row.lastSeenAt,
181
+ expiresAt: new Date(Math.min(row.expiresAt.getTime(), row.lastSeenAt.getTime() + this.options.idleTimeout * 1000)),
182
+ ...(row.userAgent ? { userAgent: row.userAgent } : {}),
183
+ ...(row.ip ? { ip: row.ip } : {}),
184
+ }));
185
+ }
186
+ async revokeSession(principal, sessionId) {
187
+ await this.sessions.delete({ id: sessionId, userId: Number(principal.user.id) });
188
+ }
189
+ async revokeOtherSessions(principal) {
190
+ const userId = Number(principal.user.id);
191
+ if (principal.sessionId)
192
+ await this.sessions.delete({ userId, id: Not(principal.sessionId) });
193
+ else
194
+ await this.sessions.delete({ userId });
195
+ }
196
+ async changePassword(principal, input) {
197
+ const user = await this.users.findOne({ where: { id: Number(principal.user.id) } });
198
+ if (!user)
199
+ throw new AdminUnauthenticatedError();
200
+ if (!(await verifyPassword(input.current, user.passwordHash)))
201
+ throw new AdminValidationError({ current: ['is wrong'] }, 'The current password is wrong');
202
+ const problems = passwordProblems(input.next, { username: user.username }, this.options.password);
203
+ if (problems.length > 0)
204
+ throw new AdminValidationError({ next: problems }, 'Choose another password');
205
+ await this.users.update({ id: user.id }, { passwordHash: await hashPassword(input.next, this.options.scrypt) });
206
+ // Someone who knew the old password is signed out everywhere else.
207
+ await this.revokeOtherSessions(principal);
208
+ }
209
+ // ---- two-factor sign-in (spec §7) ------------------------------------------------------------------------------
210
+ async userOf(principal) {
211
+ const user = await this.users.findOne({ where: { id: Number(principal.user.id) } });
212
+ if (!user)
213
+ throw new AdminUnauthenticatedError();
214
+ return user;
215
+ }
216
+ async checkPassword(user, password) {
217
+ if (!(await verifyPassword(password, user.passwordHash)))
218
+ throw new AdminValidationError({ password: ['is wrong'] }, 'The password is wrong');
219
+ }
220
+ async twoFactorStatus(principal) {
221
+ const user = await this.userOf(principal);
222
+ return { enabled: user.totpSecret !== null, recoveryCodesLeft: user.totpSecret ? parseHashes(user.recoveryCodes).length : 0 };
223
+ }
224
+ async beginTwoFactor(principal) {
225
+ const user = await this.userOf(principal);
226
+ if (user.totpSecret)
227
+ throw new AdminValidationError({ code: ['two-factor sign-in is already on'] }, 'Two-factor sign-in is already on');
228
+ if (!this.box.encrypts)
229
+ this.warnOnce('two-factor keys are stored unencrypted; set builtinAuth({ secretKey }) to encrypt them');
230
+ const secret = newTotpSecret();
231
+ await this.users.update({ id: user.id }, { totpPending: this.box.seal(base32Encode(secret)) });
232
+ return { secret: base32Encode(secret), otpauthUrl: otpauthUrl(this.options.issuer, user.username, secret) };
233
+ }
234
+ async confirmTwoFactor(principal, code) {
235
+ const user = await this.userOf(principal);
236
+ if (user.totpSecret)
237
+ throw new AdminValidationError({ code: ['two-factor sign-in is already on'] }, 'Two-factor sign-in is already on');
238
+ if (!user.totpPending)
239
+ throw new AdminValidationError({ code: ['start the setup first'] }, 'Start the setup first');
240
+ const step = verifyTotp(this.secretOf(user.totpPending), code, this.options.now().getTime());
241
+ if (step === null)
242
+ throw new AdminValidationError({ code: ['is wrong'] }, 'The code is wrong');
243
+ const codes = newRecoveryCodes();
244
+ await this.users.update({ id: user.id }, { totpSecret: user.totpPending, totpPending: null, totpLastStep: step, recoveryCodes: JSON.stringify(codes.map((item) => sha256(normalizeRecoveryCode(item)))) });
245
+ // Whoever is signed in elsewhere signed in without the second factor.
246
+ await this.revokeOtherSessions(principal);
247
+ return { recoveryCodes: codes };
248
+ }
249
+ async disableTwoFactor(principal, password) {
250
+ const user = await this.userOf(principal);
251
+ await this.checkPassword(user, password);
252
+ await this.users.update({ id: user.id }, TWO_FACTOR_OFF);
253
+ }
254
+ async newRecoveryCodes(principal, password) {
255
+ const user = await this.userOf(principal);
256
+ await this.checkPassword(user, password);
257
+ if (!user.totpSecret)
258
+ throw new AdminValidationError({ password: ['two-factor sign-in is off'] }, 'Two-factor sign-in is off');
259
+ const codes = newRecoveryCodes();
260
+ await this.users.update({ id: user.id }, { recoveryCodes: JSON.stringify(codes.map((item) => sha256(normalizeRecoveryCode(item)))) });
261
+ return { recoveryCodes: codes };
262
+ }
263
+ /** Turns off a user's second factor (the Users page) and ends their sessions. */
264
+ async resetTwoFactor(id) {
265
+ const numeric = Number(id);
266
+ if (!Number.isSafeInteger(numeric))
267
+ return;
268
+ await this.users.update({ id: numeric }, TWO_FACTOR_OFF);
269
+ await this.sessions.delete({ userId: numeric });
270
+ }
271
+ warned = new Set();
272
+ warnOnce(message) {
273
+ if (this.warned.has(message))
274
+ return;
275
+ this.warned.add(message);
276
+ this.logger.warn(`@nest-my-admin/auth: ${message}`);
277
+ }
278
+ // ---- the Users page (spec §6.7) ------------------------------------------------------------------------------
279
+ async listUsers(query) {
280
+ const qb = this.users.createQueryBuilder('u');
281
+ if (query.search) {
282
+ const pattern = `%${query.search.toLowerCase().replace(/[!%_]/g, (char) => `!${char}`)}%`;
283
+ qb.where("(LOWER(u.username) LIKE :q ESCAPE '!' OR LOWER(u.displayName) LIKE :q ESCAPE '!' OR LOWER(u.email) LIKE :q ESCAPE '!')", { q: pattern });
284
+ }
285
+ const [rows, total] = await qb.orderBy('u.username', 'ASC').skip((query.page - 1) * query.pageSize).take(query.pageSize).getManyAndCount();
286
+ return { items: rows.map(toUserRecord), total };
287
+ }
288
+ async getUser(id) {
289
+ const numeric = Number(id);
290
+ if (!Number.isSafeInteger(numeric))
291
+ return null;
292
+ const user = await this.users.findOne({ where: { id: numeric } });
293
+ return user ? toUserRecord(user) : null;
294
+ }
295
+ async createUser(input) {
296
+ const username = input.username.trim().toLowerCase();
297
+ const errors = {};
298
+ if (!USERNAME.test(username))
299
+ errors.username = ['must be letters, digits or . _ @ + - (at most 150)'];
300
+ else if (await this.users.existsBy({ username }))
301
+ errors.username = ['is taken'];
302
+ const displayName = input.displayName.trim();
303
+ if (displayName === '' || displayName.length > 150)
304
+ errors.displayName = ['must be 1–150 characters'];
305
+ const problems = passwordProblems(input.password, { username }, this.options.password);
306
+ if (problems.length > 0)
307
+ errors.password = problems;
308
+ if (input.email && input.email.length > 254)
309
+ errors.email = ['must be at most 254 characters'];
310
+ if (Object.keys(errors).length > 0)
311
+ throw new AdminValidationError(errors);
312
+ const user = await this.users.save(this.users.create({ username, displayName, email: input.email || null, passwordHash: await hashPassword(input.password, this.options.scrypt), isSuperuser: input.isSuperuser === true }));
313
+ return toUserRecord(user);
314
+ }
315
+ async updateUser(id, changes) {
316
+ const user = await this.getUser(id);
317
+ if (!user)
318
+ throw new AdminValidationError({ id: ['does not exist'] });
319
+ const update = {};
320
+ const errors = {};
321
+ if (changes.displayName !== undefined) {
322
+ if (typeof changes.displayName !== 'string' || changes.displayName.trim() === '' || changes.displayName.length > 150)
323
+ errors.displayName = ['must be 1–150 characters'];
324
+ else
325
+ update.displayName = changes.displayName.trim();
326
+ }
327
+ if (changes.email !== undefined) {
328
+ if (changes.email !== null && (typeof changes.email !== 'string' || changes.email.length > 254))
329
+ errors.email = ['must be at most 254 characters'];
330
+ else
331
+ update.email = changes.email || null;
332
+ }
333
+ for (const key of ['isActive', 'isSuperuser']) {
334
+ if (changes[key] === undefined)
335
+ continue;
336
+ if (typeof changes[key] !== 'boolean')
337
+ errors[key] = ['must be true or false'];
338
+ else
339
+ update[key] = changes[key];
340
+ }
341
+ if (Object.keys(errors).length > 0)
342
+ throw new AdminValidationError(errors);
343
+ if (Object.keys(update).length > 0)
344
+ await this.users.update({ id: Number(id) }, update);
345
+ // A deactivated user is signed out everywhere.
346
+ if (update.isActive === false)
347
+ await this.sessions.delete({ userId: Number(id) });
348
+ return (await this.getUser(id));
349
+ }
350
+ /** Sets a new password (the policy applies) and ends every session of that user. */
351
+ async setPassword(id, password) {
352
+ const user = await this.users.findOne({ where: { id: Number(id) } });
353
+ if (!user)
354
+ throw new AdminValidationError({ id: ['does not exist'] });
355
+ const problems = passwordProblems(password, { username: user.username }, this.options.password);
356
+ if (problems.length > 0)
357
+ throw new AdminValidationError({ password: problems });
358
+ await this.users.update({ id: user.id }, { passwordHash: await hashPassword(password, this.options.scrypt), failedLogins: 0, lockedUntil: null });
359
+ await this.sessions.delete({ userId: user.id });
360
+ }
361
+ setCookie(io, value, maxAge) {
362
+ const secure = this.options.secureCookie === 'auto' ? isSecureRequest(io.req) : this.options.secureCookie;
363
+ appendSetCookie(io.res, serializeCookie(this.options.cookieName, value, { path: cookiePath(io.req), httpOnly: true, secure, sameSite: 'Lax', maxAge }));
364
+ }
365
+ }
366
+ const USERNAME = /^[\p{L}\p{N}._@+-]{1,150}$/u;
367
+ function toUserRecord(user) {
368
+ return { ...toAdminUser(user), isActive: user.isActive, lastLoginAt: user.lastLoginAt, createdAt: user.createdAt, twoFactor: user.totpSecret !== null };
369
+ }
370
+ const TWO_FACTOR_OFF = { totpSecret: null, totpPending: null, totpLastStep: null, recoveryCodes: null };
371
+ function parseHashes(stored) {
372
+ if (!stored)
373
+ return [];
374
+ try {
375
+ const parsed = JSON.parse(stored);
376
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === 'string') : [];
377
+ }
378
+ catch {
379
+ return [];
380
+ }
381
+ }
382
+ /** Recovery codes are compared without case, spaces and dashes. */
383
+ function normalizeRecoveryCode(code) {
384
+ return code.toLowerCase().replace(/[\s-]/g, '');
385
+ }
386
+ /** Ten codes like `k7f3q-m2x9d` (50 random bits each). */
387
+ function newRecoveryCodes() {
388
+ return Array.from({ length: RECOVERY_CODES }, () => {
389
+ const text = base32Encode(randomBytes(7)).slice(0, 10).toLowerCase();
390
+ return `${text.slice(0, 5)}-${text.slice(5)}`;
391
+ });
392
+ }
393
+ export function toAdminUser(user) {
394
+ return {
395
+ id: user.id,
396
+ displayName: user.displayName,
397
+ username: user.username,
398
+ ...(user.email ? { email: user.email } : {}),
399
+ isSuperuser: user.isSuperuser,
400
+ };
401
+ }
402
+ /** Creates an admin user (a seed script, a CLI of your own). The password must meet the policy. */
403
+ export async function createAdminUser(dataSource, input, options = {}) {
404
+ const username = input.username.trim().toLowerCase();
405
+ if (!USERNAME.test(username))
406
+ throw new Error(`@nest-my-admin/auth: "${input.username}" is not a valid username`);
407
+ const problems = passwordProblems(input.password, { username }, options.password);
408
+ if (problems.length > 0)
409
+ throw new Error(`@nest-my-admin/auth: the password for "${username}" ${problems.join(', ')}`);
410
+ const users = dataSource.getRepository(NmaUser);
411
+ return users.save(users.create({
412
+ username,
413
+ displayName: input.displayName ?? input.username.trim(),
414
+ email: input.email ?? null,
415
+ passwordHash: await hashPassword(input.password, options.scrypt),
416
+ isSuperuser: input.isSuperuser ?? false,
417
+ }));
418
+ }
419
+ /** `AdminModule.forRoot({ auth: builtinAuth({ … }) })`. */
420
+ export function builtinAuth(options = {}) {
421
+ return AdminAuth.factory(async (moduleRef) => {
422
+ let dataSource;
423
+ try {
424
+ dataSource = moduleRef.get(getDataSourceToken(options.dataSource), { strict: false });
425
+ }
426
+ catch {
427
+ throw new Error(`@nest-my-admin/auth: no TypeORM DataSource "${options.dataSource ?? 'default'}"; import TypeOrmModule.forRoot() in the app`);
428
+ }
429
+ const adapter = new BuiltinAuthAdapter(dataSource, options);
430
+ await adapter.init(options.bootstrapSuperuser);
431
+ return adapter;
432
+ });
433
+ }
434
+ //# sourceMappingURL=builtin-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtin-auth.js","sourceRoot":"","sources":["../src/builtin-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAoC,MAAM,SAAS,CAAC;AAC1E,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,2BAA2B,EAC3B,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,eAAe,GAYhB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AAC5G,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAsC9F,MAAM,KAAK,GAAG,4BAA4B,CAAC;AAC3C,MAAM,UAAU,GAAG,uCAAuC,CAAC;AAC3D,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEnF,2GAA2G;AAC3G,SAAS,UAAU,CAAC,GAAoB;IACtC,MAAM,IAAI,GAAI,GAA8C,CAAC,OAAO,CAAC;IACrE,OAAO,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;AACnD,CAAC;AAED,SAAS,QAAQ,CAAC,GAAoB;IACpC,OAAQ,GAAyC,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,SAAS,CAAC;AACjG,CAAC;AAED,6FAA6F;AAC7F,MAAM,OAAO,kBAAkB;IAWV,UAAU;IAVZ,KAAK,CAAsB;IAC3B,QAAQ,CAAyB;IACjC,OAAO,CAAW;IAClB,OAAO,CAAc;IACrB,GAAG,CAAY;IACf,MAAM,GAAG,IAAI,MAAM,CAAC,aAAa,CAAC,CAAC;IACpD,oFAAoF;IAC5E,SAAS,CAAmB;IAEpC,YACmB,UAAsB,EACvC,OAAO,GAAuB,EAAE;0BADf,UAAU;QAG3B,KAAK,MAAM,MAAM,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;gBACpC,MAAM,IAAI,KAAK,CAAC,mFAAmF,OAAO,CAAC,UAAU,IAAI,SAAS,MAAM,MAAM,CAAC,IAAI,cAAc,CAAC,CAAC;YACrK,CAAC;QACH,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,GAAG;YACb,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,aAAa;YAC/C,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,MAAM;YAC5C,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,EAAE,GAAG,IAAI;YAC7C,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;YAC1D,eAAe,EAAE,OAAO,CAAC,OAAO,EAAE,QAAQ,IAAI,CAAC;YAC/C,SAAS,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC,GAAG,MAAM;YACpD,sBAAsB,EAAE,OAAO,CAAC,sBAAsB,IAAI,EAAE;YAC5D,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE;YAChC,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;YACtC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,MAAM;YAChC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,eAAe;SAC1C,CAAC;QACF,IAAI,CAAC,GAAG,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;IAC9E,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,IAAI,CAAC,SAAoD;QAC7D,IAAI,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;YAClD,MAAM,eAAe,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,GAAG,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAChJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,GAAoB;QACrC,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG;YAAE,OAAO,IAAI,CAAC;QAC9C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAChH,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;QACjF,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;YAC1G,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC;QACd,CAAC;QACD,iDAAiD;QACjD,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,MAAM;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/H,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;IAClG,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAiB,EAAE,EAAU;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,KAAK,CAAC,OAAO;YAAE,MAAM,IAAI,mBAAmB,CAAC,wDAAwD,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAE9H,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC/D,kFAAkF;QAClF,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACtF,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,YAAY,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QACnG,MAAM,MAAM,GAAG,IAAI,EAAE,WAAW,KAAK,IAAI,IAAI,IAAI,EAAE,WAAW,KAAK,SAAS,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;QAE3H,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAClD,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC9D,MAAM,IAAI,yBAAyB,CAAC,KAAK,CAAC,CAAC;QAC7C,CAAC;QAED,0GAA0G;QAC1G,MAAM,MAAM,GAAqB,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;gBAAE,MAAM,IAAI,2BAA2B,EAAE,CAAC;YACrE,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;YAChH,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;gBACzD,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;gBACxD,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;oBAC7B,MAAM,IAAI,2BAA2B,CAAC,UAAU,CAAC,CAAC;gBACpD,CAAC;gBACD,MAAM,CAAC,aAAa,GAAG,SAAS,CAAC;YACnC,CAAC;QACH,CAAC;QAED,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QAC9G,gDAAgD;QAChD,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1E,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YACnC,EAAE,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;YACnC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC;YACxB,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,SAAS,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;YAChD,SAAS,EAAE,GAAG;YACd,UAAU,EAAE,GAAG;YACf,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;YACxE,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI;YAC3E,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;SAClC,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QACxD,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;IAC1F,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,IAAa,EAAE,GAAS;QAC3C,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;QACvB,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;YACtD,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC/G,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,SAAS,CAAC,IAAa,EAAE,IAAY;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK;aAC5B,kBAAkB,EAAE;aACpB,MAAM,EAAE;aACR,GAAG,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;aAC3B,KAAK,CAAC,UAAU,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;aAClC,QAAQ,CAAC,IAAI,MAAM,eAAe,MAAM,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC;aAC9D,OAAO,EAAE,CAAC;QACb,OAAO,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC;IAC9D,CAAC;IAEO,QAAQ,CAAC,MAAc;QAC7B,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,2FAA2F;IACnF,eAAe,CAAC,IAAa,EAAE,IAAY;QACjD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAyB,EAAE,EAAU;QAChD,IAAI,SAAS,CAAC,SAAS;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAyB;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QACvH,OAAO,IAAI;aACR,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,GAAG,IAAI,GAAG,GAAG,CAAC;aAClH,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACb,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;YAClH,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtD,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClC,CAAC,CAAC,CAAC;IACR,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,SAAyB,EAAE,SAAiB;QAC9D,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,SAAyB;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzC,IAAI,SAAS,CAAC,SAAS;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;;YACzF,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,SAAyB,EAAE,KAAwC;QACtF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACpF,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,yBAAyB,EAAE,CAAC;QACjD,IAAI,CAAC,CAAC,MAAM,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,OAAO,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,+BAA+B,CAAC,CAAC;QAC1J,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAClG,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,yBAAyB,CAAC,CAAC;QACvG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAChH,mEAAmE;QACnE,MAAM,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IAED,mHAAmH;IAE3G,KAAK,CAAC,MAAM,CAAC,SAAyB;QAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACpF,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,yBAAyB,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,IAAa,EAAE,QAAgB;QACzD,IAAI,CAAC,CAAC,MAAM,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,QAAQ,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,uBAAuB,CAAC,CAAC;IAChJ,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,SAAyB;QAC7C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1C,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,iBAAiB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChI,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,SAAyB;QAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,IAAI,EAAE,CAAC,kCAAkC,CAAC,EAAE,EAAE,kCAAkC,CAAC,CAAC;QACxI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,CAAC,wFAAwF,CAAC,CAAC;QAChI,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/F,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;IAC9G,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,SAAyB,EAAE,IAAY;QAC5D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,IAAI,EAAE,CAAC,kCAAkC,CAAC,EAAE,EAAE,kCAAkC,CAAC,CAAC;QACxI,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,IAAI,EAAE,CAAC,uBAAuB,CAAC,EAAE,EAAE,uBAAuB,CAAC,CAAC;QACpH,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7F,IAAI,IAAI,KAAK,IAAI;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,mBAAmB,CAAC,CAAC;QAC/F,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;QACjC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3M,sEAAsE;QACtE,MAAM,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC1C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,SAAyB,EAAE,QAAgB;QAChE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACzC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,cAAc,CAAC,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,SAAyB,EAAE,QAAgB;QAChE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,QAAQ,EAAE,CAAC,2BAA2B,CAAC,EAAE,EAAE,2BAA2B,CAAC,CAAC;QAC/H,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;QACjC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtI,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IAClC,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,cAAc,CAAC,EAAU;QAC7B,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,OAAO;QAC3C,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;QACzD,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAClD,CAAC;IAEgB,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,QAAQ,CAAC,OAAe;QAC9B,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO;QACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,iHAAiH;IAEjH,KAAK,CAAC,SAAS,CAAC,KAA0D;QACxE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC;YAC1F,EAAE,CAAC,KAAK,CAAC,wHAAwH,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACrJ,CAAC;QACD,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,eAAe,EAAE,CAAC;QAC3I,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU;QACtB,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAChD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,KAAmB;QAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrD,MAAM,MAAM,GAA6B,EAAE,CAAC;QAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,MAAM,CAAC,QAAQ,GAAG,CAAC,oDAAoD,CAAC,CAAC;aAClG,IAAI,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;YAAE,MAAM,CAAC,QAAQ,GAAG,CAAC,UAAU,CAAC,CAAC;QACjF,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,WAAW,KAAK,EAAE,IAAI,WAAW,CAAC,MAAM,GAAG,GAAG;YAAE,MAAM,CAAC,WAAW,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACtG,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACpD,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG;YAAE,MAAM,CAAC,KAAK,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC/F,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC3E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAChC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,IAAI,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC,CACzL,CAAC;QACF,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,EAAU,EAAE,OAAyB;QACpD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACtE,MAAM,MAAM,GAAqB,EAAE,CAAC;QACpC,MAAM,MAAM,GAA6B,EAAE,CAAC;QAC5C,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YACtC,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,OAAO,CAAC,WAAW,CAAC,MAAM,GAAG,GAAG;gBAAE,MAAM,CAAC,WAAW,GAAG,CAAC,0BAA0B,CAAC,CAAC;;gBACnK,MAAM,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACvD,CAAC;QACD,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC;gBAAE,MAAM,CAAC,KAAK,GAAG,CAAC,gCAAgC,CAAC,CAAC;;gBAC9I,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC;QAC5C,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAU,EAAE,CAAC;YACvD,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS;gBAAE,SAAS;YACzC,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS;gBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;;gBAC1E,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC3E,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACxF,+CAA+C;QAC/C,IAAI,MAAM,CAAC,QAAQ,KAAK,KAAK;YAAE,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAClF,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAE,CAAC;IACnC,CAAC;IAED,oFAAoF;IACpF,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,QAAgB;QAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACtE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChG,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChF,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAClJ,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAClD,CAAC;IAEO,SAAS,CAAC,EAAiD,EAAE,KAAa,EAAE,MAAc;QAChG,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,GAA4C,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC;QACnJ,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1J,CAAC;CACF;AAED,MAAM,QAAQ,GAAG,6BAA6B,CAAC;AAE/C,SAAS,YAAY,CAAC,IAAa;IACjC,OAAO,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;AAC1J,CAAC;AAED,MAAM,cAAc,GAAqB,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;AAE1H,SAAS,WAAW,CAAC,MAAqB;IACxC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3C,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,mEAAmE;AACnE,SAAS,qBAAqB,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,0DAA0D;AAC1D,SAAS,gBAAgB;IACvB,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE,GAAG,EAAE;QACjD,MAAM,IAAI,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACrE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,WAAW,EAAE,IAAI,CAAC,WAAW;KAC9B,CAAC;AACJ,CAAC;AAED,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAAsB,EACtB,KAA0G,EAC1G,OAAO,GAA0D,EAAE;IAEnE,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,CAAC,QAAQ,2BAA2B,CAAC,CAAC;IAClH,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,QAAQ,KAAK,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvH,MAAM,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,IAAI,CACf,KAAK,CAAC,MAAM,CAAC;QACX,QAAQ;QACR,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE;QACvD,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,IAAI;QAC1B,YAAY,EAAE,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC;QAChE,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,KAAK;KACxC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW,CAAC,OAAO,GAAuB,EAAE;IAC1D,OAAO,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,SAAoB,EAAE,EAAE;QACtD,IAAI,UAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,UAAU,GAAG,SAAS,CAAC,GAAG,CAAa,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAW,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9G,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,+CAA+C,OAAO,CAAC,UAAU,IAAI,SAAS,8CAA8C,CAAC,CAAC;QAChJ,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC5D,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC/C,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,41 @@
1
+ /** An admin user (spec §7). Usernames are stored lower-case. */
2
+ export declare class NmaUser {
3
+ id: number;
4
+ username: string;
5
+ email: string | null;
6
+ displayName: string;
7
+ /** `scrypt$N$r$p$salt$hash`; never sent anywhere. */
8
+ passwordHash: string;
9
+ isSuperuser: boolean;
10
+ /** Inactive users cannot sign in, and their sessions stop working. */
11
+ isActive: boolean;
12
+ failedLogins: number;
13
+ lockedUntil: Date | null;
14
+ lastLoginAt: Date | null;
15
+ /** The TOTP key while two-factor sign-in is on (sealed with `secretKey` when one is set). */
16
+ totpSecret: string | null;
17
+ /** A key being set up, until it is confirmed with a code. */
18
+ totpPending: string | null;
19
+ /** The last 30-second step a code was accepted for: codes are never accepted twice. */
20
+ totpLastStep: number | null;
21
+ /** SHA-256 hashes of the unused recovery codes (JSON array). */
22
+ recoveryCodes: string | null;
23
+ createdAt: Date;
24
+ }
25
+ /** A signed-in browser. Only the SHA-256 of the session token is stored. */
26
+ export declare class NmaSession {
27
+ /** Public id (the account page shows and revokes sessions by it); unrelated to the token. */
28
+ id: string;
29
+ tokenHash: string;
30
+ userId: number;
31
+ user: NmaUser;
32
+ csrfToken: string;
33
+ createdAt: Date;
34
+ lastSeenAt: Date;
35
+ /** The absolute limit; the idle limit is `lastSeenAt` + the idle timeout. */
36
+ expiresAt: Date;
37
+ userAgent: string | null;
38
+ ip: string | null;
39
+ }
40
+ /** Add these to the TypeORM entities of the DataSource the built-in auth uses. */
41
+ export declare const ADMIN_AUTH_ENTITIES: (typeof NmaUser | typeof NmaSession)[];
@@ -0,0 +1,162 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Column, CreateDateColumn, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
11
+ /** An admin user (spec §7). Usernames are stored lower-case. */
12
+ let NmaUser = class NmaUser {
13
+ id;
14
+ username;
15
+ email;
16
+ displayName;
17
+ /** `scrypt$N$r$p$salt$hash`; never sent anywhere. */
18
+ passwordHash;
19
+ isSuperuser;
20
+ /** Inactive users cannot sign in, and their sessions stop working. */
21
+ isActive;
22
+ failedLogins;
23
+ lockedUntil;
24
+ lastLoginAt;
25
+ /** The TOTP key while two-factor sign-in is on (sealed with `secretKey` when one is set). */
26
+ totpSecret;
27
+ /** A key being set up, until it is confirmed with a code. */
28
+ totpPending;
29
+ /** The last 30-second step a code was accepted for: codes are never accepted twice. */
30
+ totpLastStep;
31
+ /** SHA-256 hashes of the unused recovery codes (JSON array). */
32
+ recoveryCodes;
33
+ createdAt;
34
+ };
35
+ __decorate([
36
+ PrimaryGeneratedColumn(),
37
+ __metadata("design:type", Number)
38
+ ], NmaUser.prototype, "id", void 0);
39
+ __decorate([
40
+ Column({ length: 150, unique: true }),
41
+ __metadata("design:type", String)
42
+ ], NmaUser.prototype, "username", void 0);
43
+ __decorate([
44
+ Column({ type: 'varchar', length: 254, nullable: true }),
45
+ __metadata("design:type", Object)
46
+ ], NmaUser.prototype, "email", void 0);
47
+ __decorate([
48
+ Column({ length: 150 }),
49
+ __metadata("design:type", String)
50
+ ], NmaUser.prototype, "displayName", void 0);
51
+ __decorate([
52
+ Column({ length: 255 }),
53
+ __metadata("design:type", String)
54
+ ], NmaUser.prototype, "passwordHash", void 0);
55
+ __decorate([
56
+ Column({ default: false }),
57
+ __metadata("design:type", Boolean)
58
+ ], NmaUser.prototype, "isSuperuser", void 0);
59
+ __decorate([
60
+ Column({ default: true }),
61
+ __metadata("design:type", Boolean)
62
+ ], NmaUser.prototype, "isActive", void 0);
63
+ __decorate([
64
+ Column({ type: 'int', default: 0 }),
65
+ __metadata("design:type", Number)
66
+ ], NmaUser.prototype, "failedLogins", void 0);
67
+ __decorate([
68
+ Column({ type: Date, nullable: true }),
69
+ __metadata("design:type", Object)
70
+ ], NmaUser.prototype, "lockedUntil", void 0);
71
+ __decorate([
72
+ Column({ type: Date, nullable: true }),
73
+ __metadata("design:type", Object)
74
+ ], NmaUser.prototype, "lastLoginAt", void 0);
75
+ __decorate([
76
+ Column({ type: 'varchar', length: 255, nullable: true }),
77
+ __metadata("design:type", Object)
78
+ ], NmaUser.prototype, "totpSecret", void 0);
79
+ __decorate([
80
+ Column({ type: 'varchar', length: 255, nullable: true }),
81
+ __metadata("design:type", Object)
82
+ ], NmaUser.prototype, "totpPending", void 0);
83
+ __decorate([
84
+ Column({ type: 'int', nullable: true }),
85
+ __metadata("design:type", Object)
86
+ ], NmaUser.prototype, "totpLastStep", void 0);
87
+ __decorate([
88
+ Column({ type: 'text', nullable: true }),
89
+ __metadata("design:type", Object)
90
+ ], NmaUser.prototype, "recoveryCodes", void 0);
91
+ __decorate([
92
+ CreateDateColumn(),
93
+ __metadata("design:type", Date)
94
+ ], NmaUser.prototype, "createdAt", void 0);
95
+ NmaUser = __decorate([
96
+ Entity('nma_user')
97
+ ], NmaUser);
98
+ export { NmaUser };
99
+ /** A signed-in browser. Only the SHA-256 of the session token is stored. */
100
+ let NmaSession = class NmaSession {
101
+ /** Public id (the account page shows and revokes sessions by it); unrelated to the token. */
102
+ id;
103
+ tokenHash;
104
+ userId;
105
+ user;
106
+ csrfToken;
107
+ createdAt;
108
+ lastSeenAt;
109
+ /** The absolute limit; the idle limit is `lastSeenAt` + the idle timeout. */
110
+ expiresAt;
111
+ userAgent;
112
+ ip;
113
+ };
114
+ __decorate([
115
+ PrimaryColumn({ type: 'varchar', length: 32 }),
116
+ __metadata("design:type", String)
117
+ ], NmaSession.prototype, "id", void 0);
118
+ __decorate([
119
+ Column({ type: 'varchar', length: 64, unique: true }),
120
+ __metadata("design:type", String)
121
+ ], NmaSession.prototype, "tokenHash", void 0);
122
+ __decorate([
123
+ Column({ type: 'int' }),
124
+ __metadata("design:type", Number)
125
+ ], NmaSession.prototype, "userId", void 0);
126
+ __decorate([
127
+ ManyToOne(() => NmaUser, { onDelete: 'CASCADE' }),
128
+ JoinColumn({ name: 'userId' }),
129
+ __metadata("design:type", NmaUser)
130
+ ], NmaSession.prototype, "user", void 0);
131
+ __decorate([
132
+ Column({ type: 'varchar', length: 64 }),
133
+ __metadata("design:type", String)
134
+ ], NmaSession.prototype, "csrfToken", void 0);
135
+ __decorate([
136
+ Column({ type: Date }),
137
+ __metadata("design:type", Date)
138
+ ], NmaSession.prototype, "createdAt", void 0);
139
+ __decorate([
140
+ Column({ type: Date }),
141
+ __metadata("design:type", Date)
142
+ ], NmaSession.prototype, "lastSeenAt", void 0);
143
+ __decorate([
144
+ Column({ type: Date }),
145
+ __metadata("design:type", Date)
146
+ ], NmaSession.prototype, "expiresAt", void 0);
147
+ __decorate([
148
+ Column({ type: 'varchar', length: 300, nullable: true }),
149
+ __metadata("design:type", Object)
150
+ ], NmaSession.prototype, "userAgent", void 0);
151
+ __decorate([
152
+ Column({ type: 'varchar', length: 64, nullable: true }),
153
+ __metadata("design:type", Object)
154
+ ], NmaSession.prototype, "ip", void 0);
155
+ NmaSession = __decorate([
156
+ Entity('nma_session'),
157
+ Index(['userId'])
158
+ ], NmaSession);
159
+ export { NmaSession };
160
+ /** Add these to the TypeORM entities of the DataSource the built-in auth uses. */
161
+ export const ADMIN_AUTH_ENTITIES = [NmaUser, NmaSession];
162
+ //# sourceMappingURL=entities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entities.js","sourceRoot":"","sources":["../src/entities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { ADMIN_AUTH_ENTITIES, NmaSession, NmaUser } from './entities.js';
2
+ export { BuiltinAuthAdapter, builtinAuth, createAdminUser, toAdminUser, type BuiltinAuthOptions } from './builtin-auth.js';
3
+ export { hashPassword, passwordProblems, verifyPassword, type PasswordPolicy } from './password.js';
4
+ export { totpCode } from './totp.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { ADMIN_AUTH_ENTITIES, NmaSession, NmaUser } from './entities.js';
2
+ export { BuiltinAuthAdapter, builtinAuth, createAdminUser, toAdminUser } from './builtin-auth.js';
3
+ export { hashPassword, passwordProblems, verifyPassword } from './password.js';
4
+ export { totpCode } from './totp.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAA2B,MAAM,mBAAmB,CAAC;AAC3H,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,24 @@
1
+ /** scrypt cost: N = 2^15, r = 8, p = 1 (about 32 MB and tens of milliseconds per hash). */
2
+ export declare const SCRYPT: {
3
+ N: number;
4
+ r: number;
5
+ p: number;
6
+ keylen: number;
7
+ };
8
+ /** `scrypt$N$r$p$salt$hash` (base64): self-describing, so the cost can grow without breaking stored hashes. */
9
+ export declare function hashPassword(password: string, cost?: {
10
+ N: number;
11
+ r: number;
12
+ p: number;
13
+ keylen: number;
14
+ }): Promise<string>;
15
+ /** Constant-time check; a malformed or foreign hash is simply `false`. */
16
+ export declare function verifyPassword(password: string, stored: string): Promise<boolean>;
17
+ export interface PasswordPolicy {
18
+ /** Default 10. */
19
+ minLength?: number;
20
+ }
21
+ /** Problems with a new password (empty when it is acceptable). */
22
+ export declare function passwordProblems(password: string, context: {
23
+ username?: string;
24
+ }, policy?: PasswordPolicy): string[];
@@ -0,0 +1,41 @@
1
+ import { randomBytes, scrypt as scryptCallback, timingSafeEqual } from 'node:crypto';
2
+ const scrypt = (password, salt, keylen, options) => new Promise((resolve, reject) => scryptCallback(password, salt, keylen, options, (error, key) => (error ? reject(error) : resolve(key))));
3
+ /** scrypt cost: N = 2^15, r = 8, p = 1 (about 32 MB and tens of milliseconds per hash). */
4
+ export const SCRYPT = { N: 32_768, r: 8, p: 1, keylen: 64 };
5
+ function maxmem(N, r, p) {
6
+ return 128 * N * r * p + 16 * 1024 * 1024;
7
+ }
8
+ /** `scrypt$N$r$p$salt$hash` (base64): self-describing, so the cost can grow without breaking stored hashes. */
9
+ export async function hashPassword(password, cost = SCRYPT) {
10
+ const salt = randomBytes(16);
11
+ const key = await scrypt(password.normalize('NFKC'), salt, cost.keylen, { N: cost.N, r: cost.r, p: cost.p, maxmem: maxmem(cost.N, cost.r, cost.p) });
12
+ return `scrypt$${cost.N}$${cost.r}$${cost.p}$${salt.toString('base64')}$${key.toString('base64')}`;
13
+ }
14
+ /** Constant-time check; a malformed or foreign hash is simply `false`. */
15
+ export async function verifyPassword(password, stored) {
16
+ const parts = stored.split('$');
17
+ if (parts.length !== 6 || parts[0] !== 'scrypt')
18
+ return false;
19
+ const [N, r, p] = parts.slice(1, 4).map(Number);
20
+ if (![N, r, p].every((value) => Number.isInteger(value) && value > 0) || N > 2 ** 20 || r > 32 || p > 16)
21
+ return false;
22
+ const salt = Buffer.from(parts[4], 'base64');
23
+ const expected = Buffer.from(parts[5], 'base64');
24
+ if (salt.length < 8 || expected.length < 16)
25
+ return false;
26
+ const key = await scrypt(password.normalize('NFKC'), salt, expected.length, { N, r, p, maxmem: maxmem(N, r, p) });
27
+ return timingSafeEqual(key, expected);
28
+ }
29
+ /** Problems with a new password (empty when it is acceptable). */
30
+ export function passwordProblems(password, context, policy = {}) {
31
+ const minLength = policy.minLength ?? 10;
32
+ const problems = [];
33
+ if ([...password].length < minLength)
34
+ problems.push(`must be at least ${minLength} characters`);
35
+ if ([...password].length > 256)
36
+ problems.push('must be at most 256 characters');
37
+ if (context.username && password.trim().toLowerCase() === context.username.trim().toLowerCase())
38
+ problems.push('must not be the username');
39
+ return problems;
40
+ }
41
+ //# sourceMappingURL=password.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password.js","sourceRoot":"","sources":["../src/password.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /** Fixed-window counter per key (in memory, per process): `limit` hits per `windowMs`. */
2
+ export declare class RateLimiter {
3
+ private readonly limit;
4
+ private readonly windowMs;
5
+ private readonly hits;
6
+ constructor(limit: number, windowMs: number);
7
+ /** Counts one hit; `retryAfter` (seconds) is set when the key is over its limit. */
8
+ hit(key: string, now?: number): {
9
+ allowed: boolean;
10
+ retryAfter: number;
11
+ };
12
+ }
@@ -0,0 +1,25 @@
1
+ /** Fixed-window counter per key (in memory, per process): `limit` hits per `windowMs`. */
2
+ export class RateLimiter {
3
+ limit;
4
+ windowMs;
5
+ hits = new Map();
6
+ constructor(limit, windowMs) {
7
+ this.limit = limit;
8
+ this.windowMs = windowMs;
9
+ }
10
+ /** Counts one hit; `retryAfter` (seconds) is set when the key is over its limit. */
11
+ hit(key, now = Date.now()) {
12
+ if (this.hits.size > 10_000)
13
+ for (const [name, entry] of this.hits)
14
+ if (entry.resetAt <= now)
15
+ this.hits.delete(name);
16
+ let entry = this.hits.get(key);
17
+ if (!entry || entry.resetAt <= now) {
18
+ entry = { count: 0, resetAt: now + this.windowMs };
19
+ this.hits.set(key, entry);
20
+ }
21
+ entry.count += 1;
22
+ return entry.count > this.limit ? { allowed: false, retryAfter: Math.ceil((entry.resetAt - now) / 1000) } : { allowed: true, retryAfter: 0 };
23
+ }
24
+ }
25
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,MAAM,OAAO,WAAW;IAIH,KAAK;IACL,QAAQ;IAJV,IAAI,GAAG,IAAI,GAAG,EAA8C,CAAC;IAE9E,YACmB,KAAa,EACb,QAAgB;qBADhB,KAAK;wBACL,QAAQ;IACxB,CAAC;IAEJ,oFAAoF;IACpF,GAAG,CAAC,GAAW,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QAC/B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,MAAM;YAAE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI;gBAAE,IAAI,KAAK,CAAC,OAAO,IAAI,GAAG;oBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrH,IAAI,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;YACnC,KAAK,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC5B,CAAC;QACD,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;QACjB,OAAO,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC/I,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Seals small secrets (TOTP keys) at rest with AES-256-GCM. Without a key, values are stored as they are; values
3
+ * stored before a key was set are still read, and sealed on their next write.
4
+ */
5
+ export declare class SecretBox {
6
+ private readonly key?;
7
+ /** `key`: 32 bytes, base64 (`openssl rand -base64 32`). */
8
+ constructor(key?: string);
9
+ get encrypts(): boolean;
10
+ seal(plain: string): string;
11
+ open(stored: string): string;
12
+ }
@@ -0,0 +1,40 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
2
+ const PREFIX = 'v1:';
3
+ /**
4
+ * Seals small secrets (TOTP keys) at rest with AES-256-GCM. Without a key, values are stored as they are; values
5
+ * stored before a key was set are still read, and sealed on their next write.
6
+ */
7
+ export class SecretBox {
8
+ key;
9
+ /** `key`: 32 bytes, base64 (`openssl rand -base64 32`). */
10
+ constructor(key) {
11
+ if (key === undefined)
12
+ return;
13
+ const bytes = Buffer.from(key, 'base64');
14
+ if (bytes.length !== 32)
15
+ throw new Error('@nest-my-admin/auth: secretKey must be 32 bytes, base64 encoded (openssl rand -base64 32)');
16
+ this.key = bytes;
17
+ }
18
+ get encrypts() {
19
+ return this.key !== undefined;
20
+ }
21
+ seal(plain) {
22
+ if (!this.key)
23
+ return plain;
24
+ const iv = randomBytes(12);
25
+ const cipher = createCipheriv('aes-256-gcm', this.key, iv);
26
+ const body = Buffer.concat([cipher.update(plain, 'utf8'), cipher.final()]);
27
+ return PREFIX + Buffer.concat([iv, cipher.getAuthTag(), body]).toString('base64url');
28
+ }
29
+ open(stored) {
30
+ if (!stored.startsWith(PREFIX))
31
+ return stored;
32
+ if (!this.key)
33
+ throw new Error('@nest-my-admin/auth: a two-factor secret is encrypted, but no secretKey is configured');
34
+ const raw = Buffer.from(stored.slice(PREFIX.length), 'base64url');
35
+ const decipher = createDecipheriv('aes-256-gcm', this.key, raw.subarray(0, 12));
36
+ decipher.setAuthTag(raw.subarray(12, 28));
37
+ return Buffer.concat([decipher.update(raw.subarray(28)), decipher.final()]).toString('utf8');
38
+ }
39
+ }
40
+ //# sourceMappingURL=secret-box.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-box.js","sourceRoot":"","sources":["../src/secret-box.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE5E,MAAM,MAAM,GAAG,KAAK,CAAC;AAErB;;;GAGG;AACH,MAAM,OAAO,SAAS;IACH,GAAG,CAAU;IAE9B,2DAA2D;IAC3D,YAAY,GAAY;QACtB,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO;QAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACzC,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,2FAA2F,CAAC,CAAC;QACtI,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC;IACnB,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC;IAChC,CAAC;IAED,IAAI,CAAC,KAAa;QAChB,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;QAC5B,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC3E,OAAO,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACvF,CAAC;IAED,IAAI,CAAC,MAAc;QACjB,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,uFAAuF,CAAC,CAAC;QACxH,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC;QAClE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAChF,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/F,CAAC;CACF"}
package/dist/totp.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /** RFC 4648 base32, without padding (what authenticator apps expect). */
2
+ export declare function base32Encode(bytes: Uint8Array): string;
3
+ /** Accepts lower case, spaces and padding; throws on other characters. */
4
+ export declare function base32Decode(text: string): Buffer;
5
+ /** RFC 4226 HOTP (HMAC-SHA1). */
6
+ export declare function hotp(secret: Uint8Array, counter: number, digits?: number): string;
7
+ export declare const TOTP_PERIOD = 30;
8
+ /** The 30-second step of a moment. */
9
+ export declare function totpStep(now: number): number;
10
+ /** RFC 6238 TOTP for a moment (ms). */
11
+ export declare function totp(secret: Uint8Array, now: number, digits?: number): string;
12
+ /**
13
+ * The step a code matches within ±`window` steps of `now`, newer than `after` (a code is never accepted twice), or
14
+ * null. Codes are compared in constant time.
15
+ */
16
+ export declare function verifyTotp(secret: Uint8Array, code: string, now: number, options?: {
17
+ after?: number | null;
18
+ window?: number;
19
+ }): number | null;
20
+ /** 20 random bytes, the size RFC 4226 recommends for SHA-1. */
21
+ export declare function newTotpSecret(): Buffer;
22
+ /** The `otpauth://` URL authenticator apps scan. */
23
+ export declare function otpauthUrl(issuer: string, account: string, secret: Uint8Array): string;
24
+ /** The current code for a base32 key (what the authenticator app shows): for tests and scripts of your own. */
25
+ export declare function totpCode(secret: string, at?: Date): string;
package/dist/totp.js ADDED
@@ -0,0 +1,89 @@
1
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
3
+ /** RFC 4648 base32, without padding (what authenticator apps expect). */
4
+ export function base32Encode(bytes) {
5
+ let bits = 0;
6
+ let value = 0;
7
+ let out = '';
8
+ for (const byte of bytes) {
9
+ value = (value << 8) | byte;
10
+ bits += 8;
11
+ while (bits >= 5) {
12
+ out += ALPHABET[(value >>> (bits - 5)) & 31];
13
+ bits -= 5;
14
+ }
15
+ }
16
+ if (bits > 0)
17
+ out += ALPHABET[(value << (5 - bits)) & 31];
18
+ return out;
19
+ }
20
+ /** Accepts lower case, spaces and padding; throws on other characters. */
21
+ export function base32Decode(text) {
22
+ const clean = text.replace(/[\s=]/g, '').toUpperCase();
23
+ let bits = 0;
24
+ let value = 0;
25
+ const out = [];
26
+ for (const char of clean) {
27
+ const index = ALPHABET.indexOf(char);
28
+ if (index < 0)
29
+ throw new Error(`"${char}" is not base32`);
30
+ value = (value << 5) | index;
31
+ bits += 5;
32
+ if (bits >= 8) {
33
+ out.push((value >>> (bits - 8)) & 255);
34
+ bits -= 8;
35
+ }
36
+ }
37
+ return Buffer.from(out);
38
+ }
39
+ /** RFC 4226 HOTP (HMAC-SHA1). */
40
+ export function hotp(secret, counter, digits = 6) {
41
+ const message = Buffer.alloc(8);
42
+ message.writeBigUInt64BE(BigInt(counter));
43
+ const hash = createHmac('sha1', secret).update(message).digest();
44
+ const offset = hash[hash.length - 1] & 15;
45
+ const code = (hash.readUInt32BE(offset) & 0x7fffffff) % 10 ** digits;
46
+ return String(code).padStart(digits, '0');
47
+ }
48
+ export const TOTP_PERIOD = 30;
49
+ /** The 30-second step of a moment. */
50
+ export function totpStep(now) {
51
+ return Math.floor(now / 1000 / TOTP_PERIOD);
52
+ }
53
+ /** RFC 6238 TOTP for a moment (ms). */
54
+ export function totp(secret, now, digits = 6) {
55
+ return hotp(secret, totpStep(now), digits);
56
+ }
57
+ /**
58
+ * The step a code matches within ±`window` steps of `now`, newer than `after` (a code is never accepted twice), or
59
+ * null. Codes are compared in constant time.
60
+ */
61
+ export function verifyTotp(secret, code, now, options = {}) {
62
+ const clean = code.replace(/\s/g, '');
63
+ if (!/^\d{6}$/.test(clean))
64
+ return null;
65
+ const current = totpStep(now);
66
+ const window = options.window ?? 1;
67
+ let matched = null;
68
+ for (let step = current - window; step <= current + window; step++) {
69
+ const expected = Buffer.from(hotp(secret, step));
70
+ if (timingSafeEqual(expected, Buffer.from(clean)) && (options.after === undefined || options.after === null || step > options.after))
71
+ matched = step;
72
+ }
73
+ return matched;
74
+ }
75
+ /** 20 random bytes, the size RFC 4226 recommends for SHA-1. */
76
+ export function newTotpSecret() {
77
+ return randomBytes(20);
78
+ }
79
+ /** The `otpauth://` URL authenticator apps scan. */
80
+ export function otpauthUrl(issuer, account, secret) {
81
+ const label = `${encodeURIComponent(issuer)}:${encodeURIComponent(account)}`;
82
+ const params = new URLSearchParams({ secret: base32Encode(secret), issuer, algorithm: 'SHA1', digits: '6', period: String(TOTP_PERIOD) });
83
+ return `otpauth://totp/${label}?${params.toString()}`;
84
+ }
85
+ /** The current code for a base32 key (what the authenticator app shows): for tests and scripts of your own. */
86
+ export function totpCode(secret, at = new Date()) {
87
+ return totp(base32Decode(secret), at.getTime());
88
+ }
89
+ //# sourceMappingURL=totp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totp.js","sourceRoot":"","sources":["../src/totp.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "@nest-my-admin/auth",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Built-in sign-in for nest-my-admin: admin users, hashed passwords, revocable sessions",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/phantomboy0/nest-my-admin.git",
9
+ "directory": "packages/auth"
10
+ },
11
+ "homepage": "https://github.com/phantomboy0/nest-my-admin#readme",
12
+ "type": "module",
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": ["dist"],
24
+ "engines": {
25
+ "node": ">=20.19.0"
26
+ },
27
+ "scripts": {
28
+ "prepack": "cp ../../LICENSE .",
29
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit"
31
+ },
32
+ "peerDependencies": {
33
+ "@nest-my-admin/core": "0.2.0",
34
+ "@nestjs/common": "^11.0.0 || ^12.0.0",
35
+ "@nestjs/core": "^11.0.0 || ^12.0.0",
36
+ "@nestjs/typeorm": "^11.0.0 || ^12.0.0",
37
+ "typeorm": "^0.3.20 || ^1.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@nest-my-admin/core": "0.2.0",
41
+ "@nestjs/common": "12.1.1",
42
+ "@nestjs/core": "12.1.1",
43
+ "@nestjs/platform-express": "12.1.1",
44
+ "@nestjs/testing": "12.1.1",
45
+ "@nestjs/typeorm": "12.0.2",
46
+ "@types/supertest": "7.2.1",
47
+ "class-transformer": "0.5.1",
48
+ "class-validator": "0.15.1",
49
+ "mysql2": "3.24.5",
50
+ "pg": "8.23.0",
51
+ "reflect-metadata": "0.2.2",
52
+ "rxjs": "7.8.2",
53
+ "sql.js": "1.14.2",
54
+ "supertest": "7.3.0",
55
+ "typeorm": "1.1.1"
56
+ }
57
+ }