@better-forge/auth 0.0.0-stage → 0.1.1
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 +21 -0
- package/README.md +4 -2
- package/dist/apiTokenRoutes.d.ts +8 -0
- package/dist/apiTokenRoutes.d.ts.map +1 -0
- package/dist/apiTokenRoutes.js +67 -0
- package/dist/apiTokenRoutes.js.map +1 -0
- package/dist/apiTokens.d.ts +25 -0
- package/dist/apiTokens.d.ts.map +1 -0
- package/dist/apiTokens.js +50 -0
- package/dist/apiTokens.js.map +1 -0
- package/dist/authRoutes.d.ts +37 -0
- package/dist/authRoutes.d.ts.map +1 -0
- package/dist/authRoutes.js +403 -0
- package/dist/authRoutes.js.map +1 -0
- package/dist/context.d.ts +98 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/createAuth.d.ts +146 -0
- package/dist/createAuth.d.ts.map +1 -0
- package/dist/createAuth.js +52 -0
- package/dist/createAuth.js.map +1 -0
- package/dist/identityConfirmation.d.ts +13 -0
- package/dist/identityConfirmation.d.ts.map +1 -0
- package/dist/identityConfirmation.js +37 -0
- package/dist/identityConfirmation.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +41 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +100 -0
- package/dist/middleware.js.map +1 -0
- package/dist/migrations.d.ts +4 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +77 -0
- package/dist/migrations.js.map +1 -0
- package/dist/module.d.ts +8 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +5 -0
- package/dist/module.js.map +1 -0
- package/dist/oidc.d.ts +85 -0
- package/dist/oidc.d.ts.map +1 -0
- package/dist/oidc.js +213 -0
- package/dist/oidc.js.map +1 -0
- package/dist/oidcConfig.d.ts +39 -0
- package/dist/oidcConfig.d.ts.map +1 -0
- package/dist/oidcConfig.js +72 -0
- package/dist/oidcConfig.js.map +1 -0
- package/dist/oidcSettings.d.ts +15 -0
- package/dist/oidcSettings.d.ts.map +1 -0
- package/dist/oidcSettings.js +51 -0
- package/dist/oidcSettings.js.map +1 -0
- package/dist/oidcSettingsRoutes.d.ts +19 -0
- package/dist/oidcSettingsRoutes.d.ts.map +1 -0
- package/dist/oidcSettingsRoutes.js +133 -0
- package/dist/oidcSettingsRoutes.js.map +1 -0
- package/dist/roles.d.ts +25 -0
- package/dist/roles.d.ts.map +1 -0
- package/dist/roles.js +44 -0
- package/dist/roles.js.map +1 -0
- package/dist/routeScope.d.ts +34 -0
- package/dist/routeScope.d.ts.map +1 -0
- package/dist/routeScope.js +32 -0
- package/dist/routeScope.js.map +1 -0
- package/dist/sessions.d.ts +46 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +159 -0
- package/dist/sessions.js.map +1 -0
- package/dist/setupRoutes.d.ts +37 -0
- package/dist/setupRoutes.d.ts.map +1 -0
- package/dist/setupRoutes.js +70 -0
- package/dist/setupRoutes.js.map +1 -0
- package/dist/signInAudit.d.ts +19 -0
- package/dist/signInAudit.d.ts.map +1 -0
- package/dist/signInAudit.js +16 -0
- package/dist/signInAudit.js.map +1 -0
- package/dist/tables.d.ts +716 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +56 -0
- package/dist/tables.js.map +1 -0
- package/dist/tokenCatalogue.d.ts +21 -0
- package/dist/tokenCatalogue.d.ts.map +1 -0
- package/dist/tokenCatalogue.js +27 -0
- package/dist/tokenCatalogue.js.map +1 -0
- package/dist/totp.d.ts +12 -0
- package/dist/totp.d.ts.map +1 -0
- package/dist/totp.js +93 -0
- package/dist/totp.js.map +1 -0
- package/dist/twoFactor.d.ts +10 -0
- package/dist/twoFactor.d.ts.map +1 -0
- package/dist/twoFactor.js +43 -0
- package/dist/twoFactor.js.map +1 -0
- package/dist/userRoutes.d.ts +13 -0
- package/dist/userRoutes.d.ts.map +1 -0
- package/dist/userRoutes.js +135 -0
- package/dist/userRoutes.js.map +1 -0
- package/package.json +66 -4
- package/src/apiTokenRoutes.ts +82 -0
- package/src/apiTokens.ts +62 -0
- package/src/authRoutes.ts +452 -0
- package/src/context.ts +85 -0
- package/src/createAuth.ts +58 -0
- package/src/identityConfirmation.ts +50 -0
- package/src/index.ts +17 -0
- package/src/middleware.ts +123 -0
- package/src/migrations.ts +77 -0
- package/src/module.ts +6 -0
- package/src/oidc.ts +291 -0
- package/src/oidcConfig.ts +96 -0
- package/src/oidcSettings.ts +60 -0
- package/src/oidcSettingsRoutes.ts +165 -0
- package/src/roles.ts +60 -0
- package/src/routeScope.ts +50 -0
- package/src/sessions.ts +209 -0
- package/src/setupRoutes.ts +100 -0
- package/src/signInAudit.ts +43 -0
- package/src/tables.ts +59 -0
- package/src/tokenCatalogue.ts +42 -0
- package/src/totp.ts +96 -0
- package/src/twoFactor.ts +49 -0
- package/src/userRoutes.ts +144 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../src/tables.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAchB,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWvB,CAAA;AAEF,2HAA2H;AAC3H,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWpB,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAavB,CAAA"}
|
package/dist/tables.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
export const users = sqliteTable('users', {
|
|
3
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
4
|
+
email: text('email').notNull().unique(),
|
|
5
|
+
passwordHash: text('password_hash').notNull(),
|
|
6
|
+
role: text('role').notNull().default('admin'),
|
|
7
|
+
createdAt: integer('created_at').notNull(),
|
|
8
|
+
// Additive columns (added via ALTER TABLE, must stay at end for sqlite-proxy position mapping)
|
|
9
|
+
mustChangePassword: integer('must_change_password').notNull().default(0),
|
|
10
|
+
totpSecret: text('totp_secret'),
|
|
11
|
+
totpEnabled: integer('totp_enabled').notNull().default(0),
|
|
12
|
+
totpRecoveryCodes: text('totp_recovery_codes'),
|
|
13
|
+
// SSO identity bound on the first OIDC sign-in; later sign-ins match on it instead of the email.
|
|
14
|
+
oidcIssuer: text('oidc_issuer'),
|
|
15
|
+
oidcSubject: text('oidc_subject'),
|
|
16
|
+
});
|
|
17
|
+
export const authSessions = sqliteTable('auth_sessions', {
|
|
18
|
+
id: text('id').primaryKey(),
|
|
19
|
+
userId: integer('user_id').notNull(),
|
|
20
|
+
csrfTokenHash: text('csrf_token_hash').notNull(),
|
|
21
|
+
createdAt: integer('created_at').notNull(),
|
|
22
|
+
lastSeenAt: integer('last_seen_at').notNull(),
|
|
23
|
+
expiresAt: integer('expires_at').notNull(),
|
|
24
|
+
// 'password' or 'oidc'. An SSO session never asks for the temporary password to be changed.
|
|
25
|
+
authMethod: text('auth_method').notNull().default('password'),
|
|
26
|
+
// When the user last proved who they are in this session: the sign-in, or a later SSO confirmation.
|
|
27
|
+
verifiedAt: integer('verified_at'),
|
|
28
|
+
});
|
|
29
|
+
/** Long-lived bearer credentials for scripts and CI. Only the SHA-256 of the token is stored; revoking deletes the row. */
|
|
30
|
+
export const apiTokens = sqliteTable('api_tokens', {
|
|
31
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
32
|
+
name: text('name').notNull(),
|
|
33
|
+
tokenHash: text('token_hash').notNull().unique(),
|
|
34
|
+
/** Leading characters of the token, so it can be recognised in the list. */
|
|
35
|
+
prefix: text('prefix').notNull(),
|
|
36
|
+
createdAt: integer('created_at').notNull(),
|
|
37
|
+
expiresAt: integer('expires_at'),
|
|
38
|
+
lastUsedAt: integer('last_used_at'),
|
|
39
|
+
/** JSON list of what the token may do, e.g. `["notes:write","tags:read"]` (the permissions are defined by the application). */
|
|
40
|
+
scopes: text('scopes').notNull(),
|
|
41
|
+
});
|
|
42
|
+
export const oidcSettings = sqliteTable('oidc_settings', {
|
|
43
|
+
id: integer('id').primaryKey(),
|
|
44
|
+
enabled: integer('enabled').notNull().default(0),
|
|
45
|
+
issuer: text('issuer').notNull().default(''),
|
|
46
|
+
clientId: text('client_id').notNull().default(''),
|
|
47
|
+
/** Stored encrypted by the application's secret box; empty when no secret is set. */
|
|
48
|
+
clientSecret: text('client_secret').notNull().default(''),
|
|
49
|
+
scopes: text('scopes').notNull().default(''),
|
|
50
|
+
redirectUri: text('redirect_uri').notNull().default(''),
|
|
51
|
+
buttonLabel: text('button_label').notNull().default(''),
|
|
52
|
+
allowUnverifiedEmail: integer('allow_unverified_email').notNull().default(0),
|
|
53
|
+
disablePasswordLogin: integer('disable_password_login').notNull().default(0),
|
|
54
|
+
updatedAt: integer('updated_at').notNull(),
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=tables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.js","sourceRoot":"","sources":["../src/tables.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TokenPreset, TokenResource } from '@better-forge/contracts';
|
|
2
|
+
export interface TokenCatalogue {
|
|
3
|
+
/** Every permission a token can hold, in the order they are listed: `<part>:read`, `<part>:write`, then the extra ones. */
|
|
4
|
+
readonly scopes: readonly string[];
|
|
5
|
+
readonly resources: readonly TokenResource[];
|
|
6
|
+
readonly presets: readonly TokenPreset[];
|
|
7
|
+
/** A clean list of known permissions (reading added wherever writing is granted), or null if one is unknown or none is given. */
|
|
8
|
+
normalize(input: unknown): string[] | null;
|
|
9
|
+
/** The permissions stored with a token. Anything that is not a known permission is dropped. */
|
|
10
|
+
parse(stored: string): string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The permissions an API token can be given. A part is a group of routes (`belongsTo(scope, part)` ties routes to
|
|
14
|
+
* it); `extraScopes` are single permissions that belong to no part, such as the one to use vault secrets.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createTokenCatalogue(options: {
|
|
17
|
+
resources: readonly TokenResource[];
|
|
18
|
+
extraScopes?: readonly string[];
|
|
19
|
+
presets?: (scopes: readonly string[]) => readonly TokenPreset[];
|
|
20
|
+
}): TokenCatalogue;
|
|
21
|
+
//# sourceMappingURL=tokenCatalogue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenCatalogue.d.ts","sourceRoot":"","sources":["../src/tokenCatalogue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAGzE,MAAM,WAAW,cAAc;IAC7B,2HAA2H;IAC3H,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,SAAS,EAAE,SAAS,aAAa,EAAE,CAAA;IAC5C,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;IACxC,iIAAiI;IACjI,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,IAAI,CAAA;IAC1C,+FAA+F;IAC/F,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAChC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,SAAS,EAAE,SAAS,aAAa,EAAE,CAAA;IACnC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,KAAK,SAAS,WAAW,EAAE,CAAA;CAChE,GAAG,cAAc,CAmBjB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { VAULT_USE_SCOPE, normalizeScopes } from '@better-forge/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* The permissions an API token can be given. A part is a group of routes (`belongsTo(scope, part)` ties routes to
|
|
4
|
+
* it); `extraScopes` are single permissions that belong to no part, such as the one to use vault secrets.
|
|
5
|
+
*/
|
|
6
|
+
export function createTokenCatalogue(options) {
|
|
7
|
+
const scopes = [
|
|
8
|
+
...options.resources.flatMap((resource) => resource.levels.map((level) => `${resource.key}:${level}`)),
|
|
9
|
+
...(options.extraScopes ?? [VAULT_USE_SCOPE]),
|
|
10
|
+
];
|
|
11
|
+
return {
|
|
12
|
+
scopes,
|
|
13
|
+
resources: options.resources,
|
|
14
|
+
presets: options.presets?.(scopes) ?? [],
|
|
15
|
+
normalize: (input) => normalizeScopes(input, scopes),
|
|
16
|
+
parse(stored) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(stored);
|
|
19
|
+
return Array.isArray(parsed) ? parsed.filter((scope) => typeof scope === 'string' && scopes.includes(scope)) : [];
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=tokenCatalogue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenCatalogue.js","sourceRoot":"","sources":["../src/tokenCatalogue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAa1E;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAIpC;IACC,MAAM,MAAM,GAAG;QACb,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC;QACtG,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,eAAe,CAAC,CAAC;KAC9C,CAAA;IACD,OAAO;QACL,MAAM;QACN,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE;QACxC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC;QACpD,KAAK,CAAC,MAAM;YACV,IAAI,CAAC;gBACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;gBAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YACpI,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,CAAA;YACX,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/totp.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function generateTotpSecret(): string;
|
|
2
|
+
export declare function verifyTotp(secret: string, input: string, now?: number): boolean;
|
|
3
|
+
export declare function generateTotpCode(secret: string, now?: number): string;
|
|
4
|
+
/** The `otpauth://` URI an authenticator app scans; `issuer` is the name it shows next to the account. */
|
|
5
|
+
export declare function totpUri(secret: string, email: string, issuer: string): string;
|
|
6
|
+
export declare function generateRecoveryCodes(): string[];
|
|
7
|
+
export declare function hashRecoveryCode(code: string): string;
|
|
8
|
+
export declare function consumeRecoveryCode(storedJson: string | null, input: string): {
|
|
9
|
+
valid: boolean;
|
|
10
|
+
remaining: string[];
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=totp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp.d.ts","sourceRoot":"","sources":["../src/totp.ts"],"names":[],"mappings":"AAyDA,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,CAInF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,MAAM,CAEzE;AAED,0GAA0G;AAC1G,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAKhD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAOrH"}
|
package/dist/totp.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
3
|
+
const PERIOD_SECONDS = 30;
|
|
4
|
+
const DIGITS = 6;
|
|
5
|
+
function encodeBase32(input) {
|
|
6
|
+
let bits = 0;
|
|
7
|
+
let value = 0;
|
|
8
|
+
let output = '';
|
|
9
|
+
for (const byte of input) {
|
|
10
|
+
value = (value << 8) | byte;
|
|
11
|
+
bits += 8;
|
|
12
|
+
while (bits >= 5) {
|
|
13
|
+
output += ALPHABET[(value >>> (bits - 5)) & 31];
|
|
14
|
+
bits -= 5;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (bits > 0)
|
|
18
|
+
output += ALPHABET[(value << (5 - bits)) & 31];
|
|
19
|
+
return output;
|
|
20
|
+
}
|
|
21
|
+
function decodeBase32(input) {
|
|
22
|
+
let bits = 0;
|
|
23
|
+
let value = 0;
|
|
24
|
+
const bytes = [];
|
|
25
|
+
for (const character of input.toUpperCase().replace(/=|\s|-/g, '')) {
|
|
26
|
+
const index = ALPHABET.indexOf(character);
|
|
27
|
+
if (index < 0)
|
|
28
|
+
throw new Error('Invalid base32 secret');
|
|
29
|
+
value = (value << 5) | index;
|
|
30
|
+
bits += 5;
|
|
31
|
+
if (bits >= 8) {
|
|
32
|
+
bytes.push((value >>> (bits - 8)) & 255);
|
|
33
|
+
bits -= 8;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Buffer.from(bytes);
|
|
37
|
+
}
|
|
38
|
+
function codeAt(secret, counter) {
|
|
39
|
+
const buffer = Buffer.alloc(8);
|
|
40
|
+
buffer.writeBigUInt64BE(BigInt(counter));
|
|
41
|
+
const digest = createHmac('sha1', decodeBase32(secret)).update(buffer).digest();
|
|
42
|
+
const offset = digest[digest.length - 1] & 0x0f;
|
|
43
|
+
const binary = ((digest[offset] & 0x7f) << 24)
|
|
44
|
+
| ((digest[offset + 1] & 0xff) << 16)
|
|
45
|
+
| ((digest[offset + 2] & 0xff) << 8)
|
|
46
|
+
| (digest[offset + 3] & 0xff);
|
|
47
|
+
return String(binary % (10 ** DIGITS)).padStart(DIGITS, '0');
|
|
48
|
+
}
|
|
49
|
+
function safeEqual(left, right) {
|
|
50
|
+
const a = Buffer.from(left);
|
|
51
|
+
const b = Buffer.from(right);
|
|
52
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
53
|
+
}
|
|
54
|
+
export function generateTotpSecret() {
|
|
55
|
+
return encodeBase32(randomBytes(20));
|
|
56
|
+
}
|
|
57
|
+
export function verifyTotp(secret, input, now = Date.now()) {
|
|
58
|
+
const normalized = input.replace(/\s/g, '');
|
|
59
|
+
const counter = Math.floor(now / 1000 / PERIOD_SECONDS);
|
|
60
|
+
return [-1, 0, 1].some((offset) => safeEqual(codeAt(secret, counter + offset), normalized));
|
|
61
|
+
}
|
|
62
|
+
export function generateTotpCode(secret, now = Date.now()) {
|
|
63
|
+
return codeAt(secret, Math.floor(now / 1000 / PERIOD_SECONDS));
|
|
64
|
+
}
|
|
65
|
+
/** The `otpauth://` URI an authenticator app scans; `issuer` is the name it shows next to the account. */
|
|
66
|
+
export function totpUri(secret, email, issuer) {
|
|
67
|
+
const label = `${issuer}:${email}`;
|
|
68
|
+
return `otpauth://totp/${encodeURIComponent(label)}?secret=${secret}&issuer=${encodeURIComponent(issuer)}&algorithm=SHA1&digits=${DIGITS}&period=${PERIOD_SECONDS}`;
|
|
69
|
+
}
|
|
70
|
+
export function generateRecoveryCodes() {
|
|
71
|
+
return Array.from({ length: 8 }, () => {
|
|
72
|
+
const value = randomBytes(10).toString('hex').toUpperCase();
|
|
73
|
+
return value.match(/.{1,5}/g).join('-');
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function hashRecoveryCode(code) {
|
|
77
|
+
return createHash('sha256').update(code.replace(/\s|-/g, '').toUpperCase()).digest('hex');
|
|
78
|
+
}
|
|
79
|
+
export function consumeRecoveryCode(storedJson, input) {
|
|
80
|
+
let stored;
|
|
81
|
+
try {
|
|
82
|
+
stored = JSON.parse(storedJson ?? '[]');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return { valid: false, remaining: [] };
|
|
86
|
+
}
|
|
87
|
+
const candidate = hashRecoveryCode(input);
|
|
88
|
+
const index = stored.findIndex((hash) => safeEqual(hash, candidate));
|
|
89
|
+
if (index < 0)
|
|
90
|
+
return { valid: false, remaining: stored };
|
|
91
|
+
return { valid: true, remaining: stored.filter((_, itemIndex) => itemIndex !== index) };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=totp.js.map
|
package/dist/totp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp.js","sourceRoot":"","sources":["../src/totp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { users } from './tables.js';
|
|
2
|
+
import type { AuthOptions } from './context.js';
|
|
3
|
+
type TwoFactorUser = Pick<typeof users.$inferSelect, 'id' | 'totpEnabled' | 'totpSecret' | 'totpRecoveryCodes'>;
|
|
4
|
+
export declare function createTwoFactor(ctx: AuthOptions): {
|
|
5
|
+
verifySecondFactor: (user: TwoFactorUser, code: string) => Promise<boolean>;
|
|
6
|
+
resetTwoFactor: (userId: number) => Promise<boolean>;
|
|
7
|
+
};
|
|
8
|
+
export type TwoFactor = ReturnType<typeof createTwoFactor>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=twoFactor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twoFactor.d.ts","sourceRoot":"","sources":["../src/twoFactor.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C,KAAK,aAAa,GAAG,IAAI,CAAC,OAAO,KAAK,CAAC,YAAY,EAAE,IAAI,GAAG,aAAa,GAAG,YAAY,GAAG,mBAAmB,CAAC,CAAA;AAE/G,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW;+BAON,aAAa,QAAQ,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;6BAgBhD,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;EAehE;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { withImmediateTransaction } from '@better-forge/db';
|
|
3
|
+
import { consumeRecoveryCode, verifyTotp } from './totp.js';
|
|
4
|
+
import { authSessions, users } from './tables.js';
|
|
5
|
+
export function createTwoFactor(ctx) {
|
|
6
|
+
const { db, sqlite, secretBox } = ctx;
|
|
7
|
+
/**
|
|
8
|
+
* Verify a TOTP or consume a recovery code. Recovery-code persistence uses a
|
|
9
|
+
* compare-and-swap update so the same code cannot win in concurrent requests.
|
|
10
|
+
*/
|
|
11
|
+
async function verifySecondFactor(user, code) {
|
|
12
|
+
if (!user.totpEnabled || !user.totpSecret)
|
|
13
|
+
return false;
|
|
14
|
+
if (verifyTotp(secretBox.decrypt(user.totpSecret), code))
|
|
15
|
+
return true;
|
|
16
|
+
const storedCodes = user.totpRecoveryCodes;
|
|
17
|
+
const recovery = consumeRecoveryCode(storedCodes, code);
|
|
18
|
+
if (!storedCodes || !recovery.valid)
|
|
19
|
+
return false;
|
|
20
|
+
const updated = await db.update(users)
|
|
21
|
+
.set({ totpRecoveryCodes: JSON.stringify(recovery.remaining) })
|
|
22
|
+
.where(and(eq(users.id, user.id), eq(users.totpRecoveryCodes, storedCodes)))
|
|
23
|
+
.returning({ id: users.id });
|
|
24
|
+
return updated.length === 1;
|
|
25
|
+
}
|
|
26
|
+
/** Clear 2FA credentials and revoke every active session for the user. */
|
|
27
|
+
async function resetTwoFactor(userId) {
|
|
28
|
+
return withImmediateTransaction(sqlite, async () => {
|
|
29
|
+
const updated = await db.update(users).set({
|
|
30
|
+
totpSecret: null,
|
|
31
|
+
totpEnabled: 0,
|
|
32
|
+
totpRecoveryCodes: null,
|
|
33
|
+
}).where(and(eq(users.id, userId), eq(users.totpEnabled, 1)))
|
|
34
|
+
.returning({ id: users.id });
|
|
35
|
+
if (updated.length !== 1)
|
|
36
|
+
return false;
|
|
37
|
+
await db.delete(authSessions).where(eq(authSessions.userId, userId));
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return { verifySecondFactor, resetTwoFactor };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=twoFactor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twoFactor.js","sourceRoot":"","sources":["../src/twoFactor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAKjD,MAAM,UAAU,eAAe,CAAC,GAAgB;IAC9C,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAA;IAErC;;;OAGG;IACH,KAAK,UAAU,kBAAkB,CAAC,IAAmB,EAAE,IAAY;QACjE,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAA;QACvD,IAAI,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QAErE,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,CAAA;QAC1C,MAAM,QAAQ,GAAG,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;QACvD,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,CAAC,KAAK;YAAE,OAAO,KAAK,CAAA;QAEjD,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;aACnC,GAAG,CAAC,EAAE,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;aAC9D,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC,CAAC;aAC3E,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;QAC9B,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAA;IAC7B,CAAC;IAED,0EAA0E;IAC1E,KAAK,UAAU,cAAc,CAAC,MAAc;QAC1C,OAAO,wBAAwB,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;YACjD,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC;gBACzC,UAAU,EAAE,IAAI;gBAChB,WAAW,EAAE,CAAC;gBACd,iBAAiB,EAAE,IAAI;aACxB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;iBAC1D,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;YAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YACtC,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;YACpE,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,CAAA;AAC/C,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { AuthOptions } from './context.js';
|
|
3
|
+
import type { Sessions } from './sessions.js';
|
|
4
|
+
import type { TwoFactor } from './twoFactor.js';
|
|
5
|
+
import type { IdentityConfirmation } from './identityConfirmation.js';
|
|
6
|
+
export declare function createUserRoutes(ctx: AuthOptions, parts: {
|
|
7
|
+
sessions: Sessions;
|
|
8
|
+
twoFactor: TwoFactor;
|
|
9
|
+
identityConfirmation: IdentityConfirmation;
|
|
10
|
+
}): {
|
|
11
|
+
userRoutes: (app: FastifyInstance) => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=userRoutes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userRoutes.d.ts","sourceRoot":"","sources":["../src/userRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAM9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AASrE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,SAAS,CAAC;IAAC,oBAAoB,EAAE,oBAAoB,CAAA;CAAE;sBASjH,eAAe;EAoH/C"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import bcrypt from 'bcryptjs';
|
|
2
|
+
import { randomInt } from 'node:crypto';
|
|
3
|
+
import { eq } from 'drizzle-orm';
|
|
4
|
+
import { users } from './tables.js';
|
|
5
|
+
import { requestIdentity } from './middleware.js';
|
|
6
|
+
function generateTempPassword() {
|
|
7
|
+
const chars = 'ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
|
|
8
|
+
let result = '';
|
|
9
|
+
for (let i = 0; i < 12; i++)
|
|
10
|
+
result += chars[randomInt(chars.length)];
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
export function createUserRoutes(ctx, parts) {
|
|
14
|
+
const { db, roles, audit } = ctx;
|
|
15
|
+
const { revokeUserSessions } = parts.sessions;
|
|
16
|
+
const { resetTwoFactor } = parts.twoFactor;
|
|
17
|
+
const { confirmIdentity } = parts.identityConfirmation;
|
|
18
|
+
// These routes act on accounts and sign-ins, so a hijacked session alone must not be enough.
|
|
19
|
+
const sensitive = { config: { rateLimit: ctx.rateLimits.sensitiveAction } };
|
|
20
|
+
async function userRoutes(app) {
|
|
21
|
+
app.get('/', async () => {
|
|
22
|
+
const all = await db.select({
|
|
23
|
+
id: users.id,
|
|
24
|
+
email: users.email,
|
|
25
|
+
role: users.role,
|
|
26
|
+
mustChangePassword: users.mustChangePassword,
|
|
27
|
+
twoFactorEnabled: users.totpEnabled,
|
|
28
|
+
oidcIssuer: users.oidcIssuer,
|
|
29
|
+
createdAt: users.createdAt,
|
|
30
|
+
}).from(users);
|
|
31
|
+
return all.map(({ mustChangePassword, oidcIssuer, ...u }) => ({
|
|
32
|
+
...u,
|
|
33
|
+
role: roles.normalize(u.role),
|
|
34
|
+
ssoLinked: !!oidcIssuer,
|
|
35
|
+
// An SSO sign-in revokes the temporary password, so the flag alone is accurate.
|
|
36
|
+
pendingTemporaryPassword: !!mustChangePassword,
|
|
37
|
+
}));
|
|
38
|
+
});
|
|
39
|
+
app.post('/', sensitive, async (req, reply) => {
|
|
40
|
+
const { email, role = roles.defaultForNewUsers } = req.body;
|
|
41
|
+
if (!email)
|
|
42
|
+
return reply.code(400).send({ error: 'Email is required' });
|
|
43
|
+
if (!roles.isValid(role))
|
|
44
|
+
return reply.code(400).send({ error: 'Invalid role' });
|
|
45
|
+
const actor = requestIdentity(req);
|
|
46
|
+
if (!await confirmIdentity(actor, req.body.currentPassword, reply))
|
|
47
|
+
return reply;
|
|
48
|
+
const existing = await db.select().from(users).where(eq(users.email, email));
|
|
49
|
+
if (existing.length > 0)
|
|
50
|
+
return reply.code(409).send({ error: 'User with this email already exists' });
|
|
51
|
+
const temporaryPassword = generateTempPassword();
|
|
52
|
+
const hash = await bcrypt.hash(temporaryPassword, 10);
|
|
53
|
+
const result = await db.insert(users).values({
|
|
54
|
+
email, passwordHash: hash, role, mustChangePassword: 1, createdAt: Date.now(),
|
|
55
|
+
}).returning({ id: users.id, email: users.email, role: users.role, createdAt: users.createdAt });
|
|
56
|
+
audit.write(audit.actor(actor), 'create', 'user', result[0].id, email, audit.snapshot({ email, role }));
|
|
57
|
+
return { ...result[0], temporaryPassword };
|
|
58
|
+
});
|
|
59
|
+
app.patch('/:id/role', sensitive, async (req, reply) => {
|
|
60
|
+
const id = Number(req.params.id);
|
|
61
|
+
const jwt = requestIdentity(req);
|
|
62
|
+
if (jwt.userId === id)
|
|
63
|
+
return reply.code(400).send({ error: 'Cannot change your own role' });
|
|
64
|
+
if (!roles.isValid(req.body.role)) {
|
|
65
|
+
return reply.code(400).send({ error: 'Invalid role' });
|
|
66
|
+
}
|
|
67
|
+
if (!await confirmIdentity(jwt, req.body.currentPassword, reply))
|
|
68
|
+
return reply;
|
|
69
|
+
const existing = (await db.select().from(users).where(eq(users.id, id)))[0];
|
|
70
|
+
const result = await db.update(users).set({ role: req.body.role }).where(eq(users.id, id))
|
|
71
|
+
.returning({ id: users.id, role: users.role });
|
|
72
|
+
if (!result.length)
|
|
73
|
+
return reply.code(404).send({ error: 'User not found' });
|
|
74
|
+
if (existing) {
|
|
75
|
+
await revokeUserSessions(id);
|
|
76
|
+
const actor = requestIdentity(req);
|
|
77
|
+
audit.write(audit.actor(actor), 'update', 'user', id, existing.email, { role: { from: existing.role, to: req.body.role } });
|
|
78
|
+
}
|
|
79
|
+
return result[0];
|
|
80
|
+
});
|
|
81
|
+
app.post('/:id/reset-password', sensitive, async (req, reply) => {
|
|
82
|
+
const id = Number(req.params.id);
|
|
83
|
+
const actor = requestIdentity(req);
|
|
84
|
+
if (!await confirmIdentity(actor, req.body?.currentPassword, reply))
|
|
85
|
+
return reply;
|
|
86
|
+
const user = (await db.select().from(users).where(eq(users.id, id)))[0];
|
|
87
|
+
if (!user)
|
|
88
|
+
return reply.code(404).send({ error: 'User not found' });
|
|
89
|
+
const temporaryPassword = generateTempPassword();
|
|
90
|
+
const hash = await bcrypt.hash(temporaryPassword, 10);
|
|
91
|
+
await db.update(users).set({
|
|
92
|
+
passwordHash: hash,
|
|
93
|
+
mustChangePassword: 1,
|
|
94
|
+
}).where(eq(users.id, id));
|
|
95
|
+
await revokeUserSessions(id);
|
|
96
|
+
audit.write(audit.actor(actor), 'update', 'user', id, user.email, { action: 'password_reset' });
|
|
97
|
+
return { temporaryPassword };
|
|
98
|
+
});
|
|
99
|
+
app.post('/:id/reset-2fa', sensitive, async (req, reply) => {
|
|
100
|
+
const id = Number(req.params.id);
|
|
101
|
+
const actor = requestIdentity(req);
|
|
102
|
+
if (!Number.isInteger(id))
|
|
103
|
+
return reply.code(400).send({ error: 'Invalid user ID' });
|
|
104
|
+
if (actor.userId === id) {
|
|
105
|
+
return reply.code(400).send({ error: 'Use account settings to disable your own two-factor authentication' });
|
|
106
|
+
}
|
|
107
|
+
if (!await confirmIdentity(actor, req.body?.currentPassword, reply))
|
|
108
|
+
return reply;
|
|
109
|
+
const target = (await db.select().from(users).where(eq(users.id, id)))[0];
|
|
110
|
+
if (!target)
|
|
111
|
+
return reply.code(404).send({ error: 'User not found' });
|
|
112
|
+
if (!target.totpEnabled)
|
|
113
|
+
return reply.code(409).send({ error: 'Two-factor authentication is not enabled for this user' });
|
|
114
|
+
if (!await resetTwoFactor(id))
|
|
115
|
+
return reply.code(409).send({ error: 'Two-factor authentication is already disabled' });
|
|
116
|
+
await audit.write(audit.actor(actor), 'update', 'user-security', id, target.email, { twoFactorReset: { from: true, to: false }, method: 'admin_recovery' });
|
|
117
|
+
return { twoFactorEnabled: false };
|
|
118
|
+
});
|
|
119
|
+
app.delete('/:id', async (req, reply) => {
|
|
120
|
+
const id = Number(req.params.id);
|
|
121
|
+
const actor = requestIdentity(req);
|
|
122
|
+
if (actor.userId === id)
|
|
123
|
+
return reply.code(400).send({ error: 'Cannot delete your own account' });
|
|
124
|
+
const existing = (await db.select().from(users).where(eq(users.id, id)))[0];
|
|
125
|
+
await revokeUserSessions(id);
|
|
126
|
+
await db.delete(users).where(eq(users.id, id));
|
|
127
|
+
if (existing) {
|
|
128
|
+
audit.write(audit.actor(actor), 'delete', 'user', id, existing.email, audit.snapshot({ email: existing.email, role: existing.role }));
|
|
129
|
+
}
|
|
130
|
+
return { success: true };
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return { userRoutes };
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=userRoutes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userRoutes.js","sourceRoot":"","sources":["../src/userRoutes.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-forge/auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Users, sessions, 2FA, SSO/OIDC, roles, API tokens and sign-in routes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/BElluu/Better-Forge.git",
|
|
9
|
+
"directory": "packages/auth"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/BElluu/Better-Forge/tree/main/packages/auth#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/BElluu/Better-Forge/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src",
|
|
31
|
+
"!src/**/*.test.*"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@fastify/cookie": "^11.1.1",
|
|
39
|
+
"@fastify/jwt": "^10.1.0",
|
|
40
|
+
"bcryptjs": "^3.0.2",
|
|
41
|
+
"drizzle-orm": "^0.45.2",
|
|
42
|
+
"fastify": "^5.3.2",
|
|
43
|
+
"openid-client": "^6.8.8",
|
|
44
|
+
"qrcode": "^1.5.4",
|
|
45
|
+
"@fastify/rate-limit": "^10.3.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^26.6.2",
|
|
49
|
+
"@types/qrcode": "^1.5.6",
|
|
50
|
+
"@fastify/cookie": "^11.1.1",
|
|
51
|
+
"@fastify/jwt": "^10.1.0",
|
|
52
|
+
"bcryptjs": "^3.0.2",
|
|
53
|
+
"drizzle-orm": "^0.45.2",
|
|
54
|
+
"fastify": "^5.3.2",
|
|
55
|
+
"openid-client": "^6.8.8",
|
|
56
|
+
"qrcode": "^1.5.4",
|
|
57
|
+
"typescript": "^5.7.3",
|
|
58
|
+
"@fastify/rate-limit": "^10.3.0"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@better-forge/audit": "0.1.1",
|
|
62
|
+
"@better-forge/contracts": "0.1.1",
|
|
63
|
+
"@better-forge/db": "0.1.1"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=26.10.0"
|
|
67
|
+
}
|
|
68
|
+
}
|