@revealui/auth 0.3.5 → 0.3.7
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/README.md +1 -1
- package/dist/server/audit-bridge.d.ts +69 -0
- package/dist/server/audit-bridge.d.ts.map +1 -0
- package/dist/server/audit-bridge.js +169 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +5 -3
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/server/mfa-enforcement.d.ts +76 -0
- package/dist/server/mfa-enforcement.d.ts.map +1 -0
- package/dist/server/mfa-enforcement.js +76 -0
- package/dist/server/providers/vercel.d.ts.map +1 -1
- package/dist/server/providers/vercel.js +6 -0
- package/package.json +11 -5
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ pnpm test
|
|
|
106
106
|
|
|
107
107
|
## Related
|
|
108
108
|
|
|
109
|
-
- [Core Package](../core/README.md) —
|
|
109
|
+
- [Core Package](../core/README.md) — Runtime engine (uses auth for access control)
|
|
110
110
|
- [DB Package](../db/README.md) — Database schema (sessions, users, rate_limits tables)
|
|
111
111
|
- [Auth Guide](../../docs/AUTH.md) — Architecture, usage patterns, and security design
|
|
112
112
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Event Bridge — Connects auth events to the audit trail.
|
|
3
|
+
*
|
|
4
|
+
* Each function wraps an auth operation with structured audit logging
|
|
5
|
+
* via the AuditSystem from @revealui/security. Uses lazy import to
|
|
6
|
+
* avoid circular dependency issues at module load time.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Record a successful login to the audit trail.
|
|
10
|
+
*
|
|
11
|
+
* @param userId - The authenticated user's ID
|
|
12
|
+
* @param ip - Client IP address
|
|
13
|
+
* @param userAgent - Client User-Agent header
|
|
14
|
+
*/
|
|
15
|
+
export declare function auditLoginSuccess(userId: string, ip: string, userAgent: string): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Record a failed login attempt to the audit trail.
|
|
18
|
+
*
|
|
19
|
+
* @param email - The email used in the failed attempt
|
|
20
|
+
* @param ip - Client IP address
|
|
21
|
+
* @param userAgent - Client User-Agent header
|
|
22
|
+
* @param reason - Why the login failed (e.g. 'invalid_password', 'account_locked')
|
|
23
|
+
*/
|
|
24
|
+
export declare function auditLoginFailure(email: string, ip: string, userAgent: string, reason: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Record a password change to the audit trail.
|
|
27
|
+
*
|
|
28
|
+
* @param userId - The user who changed their password
|
|
29
|
+
* @param ip - Client IP address
|
|
30
|
+
*/
|
|
31
|
+
export declare function auditPasswordChange(userId: string, ip: string): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Record a password reset request to the audit trail.
|
|
34
|
+
*
|
|
35
|
+
* @param email - The email for which a reset was requested
|
|
36
|
+
* @param ip - Client IP address
|
|
37
|
+
*/
|
|
38
|
+
export declare function auditPasswordReset(email: string, ip: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Record MFA being enabled on an account to the audit trail.
|
|
41
|
+
*
|
|
42
|
+
* @param userId - The user who enabled MFA
|
|
43
|
+
* @param ip - Client IP address
|
|
44
|
+
*/
|
|
45
|
+
export declare function auditMfaEnabled(userId: string, ip: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Record MFA being disabled on an account to the audit trail.
|
|
48
|
+
*
|
|
49
|
+
* @param userId - The user who disabled MFA
|
|
50
|
+
* @param ip - Client IP address
|
|
51
|
+
*/
|
|
52
|
+
export declare function auditMfaDisabled(userId: string, ip: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Record a session revocation to the audit trail.
|
|
55
|
+
*
|
|
56
|
+
* @param userId - The user whose session was revoked
|
|
57
|
+
* @param sessionId - The revoked session's ID
|
|
58
|
+
* @param ip - Client IP address
|
|
59
|
+
*/
|
|
60
|
+
export declare function auditSessionRevoked(userId: string, sessionId: string, ip: string): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Record an account lockout to the audit trail.
|
|
63
|
+
*
|
|
64
|
+
* @param email - The locked account's email
|
|
65
|
+
* @param ip - Client IP address
|
|
66
|
+
* @param failedAttempts - Number of failed attempts that triggered the lockout
|
|
67
|
+
*/
|
|
68
|
+
export declare function auditAccountLocked(email: string, ip: string, failedAttempts: number): Promise<void>;
|
|
69
|
+
//# sourceMappingURL=audit-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-bridge.d.ts","sourceRoot":"","sources":["../../src/server/audit-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgCH;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASnF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjF;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS/E;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAShF;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAUf"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Event Bridge — Connects auth events to the audit trail.
|
|
3
|
+
*
|
|
4
|
+
* Each function wraps an auth operation with structured audit logging
|
|
5
|
+
* via the AuditSystem from @revealui/security. Uses lazy import to
|
|
6
|
+
* avoid circular dependency issues at module load time.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Lazily resolve the global audit system from @revealui/security.
|
|
10
|
+
* Returns null if the module cannot be loaded (e.g. in test environments
|
|
11
|
+
* where @revealui/security is not available).
|
|
12
|
+
*/
|
|
13
|
+
async function getAudit() {
|
|
14
|
+
try {
|
|
15
|
+
const { audit } = await import('@revealui/security');
|
|
16
|
+
return audit;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Internal helper — logs an audit event, silently skipping if the
|
|
24
|
+
* audit system is unavailable.
|
|
25
|
+
*/
|
|
26
|
+
async function logAuditEvent(event) {
|
|
27
|
+
const auditSystem = await getAudit();
|
|
28
|
+
if (!auditSystem) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await auditSystem.log(event);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Record a successful login to the audit trail.
|
|
35
|
+
*
|
|
36
|
+
* @param userId - The authenticated user's ID
|
|
37
|
+
* @param ip - Client IP address
|
|
38
|
+
* @param userAgent - Client User-Agent header
|
|
39
|
+
*/
|
|
40
|
+
export async function auditLoginSuccess(userId, ip, userAgent) {
|
|
41
|
+
await logAuditEvent({
|
|
42
|
+
type: 'auth.login',
|
|
43
|
+
severity: 'low',
|
|
44
|
+
actor: { id: userId, type: 'user', ip, userAgent },
|
|
45
|
+
action: 'login',
|
|
46
|
+
result: 'success',
|
|
47
|
+
message: 'User logged in successfully',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Record a failed login attempt to the audit trail.
|
|
52
|
+
*
|
|
53
|
+
* @param email - The email used in the failed attempt
|
|
54
|
+
* @param ip - Client IP address
|
|
55
|
+
* @param userAgent - Client User-Agent header
|
|
56
|
+
* @param reason - Why the login failed (e.g. 'invalid_password', 'account_locked')
|
|
57
|
+
*/
|
|
58
|
+
export async function auditLoginFailure(email, ip, userAgent, reason) {
|
|
59
|
+
await logAuditEvent({
|
|
60
|
+
type: 'auth.failed_login',
|
|
61
|
+
severity: 'medium',
|
|
62
|
+
actor: { id: email, type: 'user', ip, userAgent },
|
|
63
|
+
action: 'login',
|
|
64
|
+
result: 'failure',
|
|
65
|
+
message: `Login failed: ${reason}`,
|
|
66
|
+
metadata: { email, reason },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Record a password change to the audit trail.
|
|
71
|
+
*
|
|
72
|
+
* @param userId - The user who changed their password
|
|
73
|
+
* @param ip - Client IP address
|
|
74
|
+
*/
|
|
75
|
+
export async function auditPasswordChange(userId, ip) {
|
|
76
|
+
await logAuditEvent({
|
|
77
|
+
type: 'auth.password_change',
|
|
78
|
+
severity: 'medium',
|
|
79
|
+
actor: { id: userId, type: 'user', ip },
|
|
80
|
+
action: 'password_change',
|
|
81
|
+
result: 'success',
|
|
82
|
+
message: 'Password changed',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Record a password reset request to the audit trail.
|
|
87
|
+
*
|
|
88
|
+
* @param email - The email for which a reset was requested
|
|
89
|
+
* @param ip - Client IP address
|
|
90
|
+
*/
|
|
91
|
+
export async function auditPasswordReset(email, ip) {
|
|
92
|
+
await logAuditEvent({
|
|
93
|
+
type: 'auth.password_reset',
|
|
94
|
+
severity: 'medium',
|
|
95
|
+
actor: { id: email, type: 'user', ip },
|
|
96
|
+
action: 'password_reset',
|
|
97
|
+
result: 'success',
|
|
98
|
+
message: 'Password reset requested',
|
|
99
|
+
metadata: { email },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Record MFA being enabled on an account to the audit trail.
|
|
104
|
+
*
|
|
105
|
+
* @param userId - The user who enabled MFA
|
|
106
|
+
* @param ip - Client IP address
|
|
107
|
+
*/
|
|
108
|
+
export async function auditMfaEnabled(userId, ip) {
|
|
109
|
+
await logAuditEvent({
|
|
110
|
+
type: 'auth.mfa_enabled',
|
|
111
|
+
severity: 'medium',
|
|
112
|
+
actor: { id: userId, type: 'user', ip },
|
|
113
|
+
action: 'mfa_enabled',
|
|
114
|
+
result: 'success',
|
|
115
|
+
message: 'MFA enabled',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Record MFA being disabled on an account to the audit trail.
|
|
120
|
+
*
|
|
121
|
+
* @param userId - The user who disabled MFA
|
|
122
|
+
* @param ip - Client IP address
|
|
123
|
+
*/
|
|
124
|
+
export async function auditMfaDisabled(userId, ip) {
|
|
125
|
+
await logAuditEvent({
|
|
126
|
+
type: 'auth.mfa_disabled',
|
|
127
|
+
severity: 'high',
|
|
128
|
+
actor: { id: userId, type: 'user', ip },
|
|
129
|
+
action: 'mfa_disabled',
|
|
130
|
+
result: 'success',
|
|
131
|
+
message: 'MFA disabled',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Record a session revocation to the audit trail.
|
|
136
|
+
*
|
|
137
|
+
* @param userId - The user whose session was revoked
|
|
138
|
+
* @param sessionId - The revoked session's ID
|
|
139
|
+
* @param ip - Client IP address
|
|
140
|
+
*/
|
|
141
|
+
export async function auditSessionRevoked(userId, sessionId, ip) {
|
|
142
|
+
await logAuditEvent({
|
|
143
|
+
type: 'security.alert',
|
|
144
|
+
severity: 'medium',
|
|
145
|
+
actor: { id: userId, type: 'user', ip },
|
|
146
|
+
action: 'session_revoked',
|
|
147
|
+
result: 'success',
|
|
148
|
+
message: 'Session revoked',
|
|
149
|
+
metadata: { sessionId },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Record an account lockout to the audit trail.
|
|
154
|
+
*
|
|
155
|
+
* @param email - The locked account's email
|
|
156
|
+
* @param ip - Client IP address
|
|
157
|
+
* @param failedAttempts - Number of failed attempts that triggered the lockout
|
|
158
|
+
*/
|
|
159
|
+
export async function auditAccountLocked(email, ip, failedAttempts) {
|
|
160
|
+
await logAuditEvent({
|
|
161
|
+
type: 'security.alert',
|
|
162
|
+
severity: 'high',
|
|
163
|
+
actor: { id: email, type: 'user', ip },
|
|
164
|
+
action: 'account_locked',
|
|
165
|
+
result: 'failure',
|
|
166
|
+
message: `Account locked after ${String(failedAttempts)} failed attempts`,
|
|
167
|
+
metadata: { email, failedAttempts },
|
|
168
|
+
});
|
|
169
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAQ,MAAM,aAAa,CAAC;AASpE;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAQ,MAAM,aAAa,CAAC;AASpE;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,YAAY,CAAC,CAuKvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAiBtD;AAED;;;;;;;;GAQG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,YAAY,CAAC,CAsLvB"}
|
package/dist/server/auth.js
CHANGED
|
@@ -12,7 +12,7 @@ import { and, eq, isNull } from 'drizzle-orm';
|
|
|
12
12
|
import { clearFailedAttempts, isAccountLocked, recordFailedAttempt } from './brute-force.js';
|
|
13
13
|
import { validatePasswordStrength } from './password-validation.js';
|
|
14
14
|
import { checkRateLimit } from './rate-limit.js';
|
|
15
|
-
import { createSession } from './session.js';
|
|
15
|
+
import { createSession, rotateSession } from './session.js';
|
|
16
16
|
/** Grace period after signup during which unverified users can still sign in (24 hours) */
|
|
17
17
|
const EMAIL_VERIFICATION_GRACE_PERIOD_MS = 24 * 60 * 60 * 1000;
|
|
18
18
|
/**
|
|
@@ -145,10 +145,12 @@ export async function signIn(email, password, options) {
|
|
|
145
145
|
mfaUserId: user.id,
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
|
-
//
|
|
148
|
+
// Rotate session: delete all existing sessions for this user, then create
|
|
149
|
+
// a fresh one. This prevents session fixation attacks where an attacker
|
|
150
|
+
// plants a session token that the victim later authenticates.
|
|
149
151
|
let token;
|
|
150
152
|
try {
|
|
151
|
-
const sessionResult = await
|
|
153
|
+
const sessionResult = await rotateSession(user.id, {
|
|
152
154
|
userAgent: options?.userAgent || 'Unknown',
|
|
153
155
|
ipAddress: options?.ipAddress || 'Unknown',
|
|
154
156
|
});
|
package/dist/server/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Inspired by Better Auth and Neon Auth patterns.
|
|
6
6
|
*/
|
|
7
7
|
export type { SignInResult, SignUpResult } from '../types.js';
|
|
8
|
+
export { auditAccountLocked, auditLoginFailure, auditLoginSuccess, auditMfaDisabled, auditMfaEnabled, auditPasswordChange, auditPasswordReset, auditSessionRevoked, } from './audit-bridge.js';
|
|
8
9
|
export { isSignupAllowed, signIn, signUp } from './auth.js';
|
|
9
10
|
export { clearFailedAttempts, configureBruteForce, getFailedAttemptCount, isAccountLocked, recordFailedAttempt, resetBruteForceConfig, } from './brute-force.js';
|
|
10
11
|
export { AuthError, AuthenticationError, DatabaseError, OAuthAccountConflictError, SessionError, TokenError, } from './errors.js';
|
|
@@ -12,6 +13,8 @@ export type { MagicLinkConfig } from './magic-link.js';
|
|
|
12
13
|
export { configureMagicLink, createMagicLink, resetMagicLinkConfig, verifyMagicLink, } from './magic-link.js';
|
|
13
14
|
export type { MFAConfig, MFADisableProof, MFASetupResult } from './mfa.js';
|
|
14
15
|
export { configureMFA, disableMFA, initiateMFASetup, isMFAEnabled, regenerateBackupCodes, resetMFAConfig, verifyBackupCode, verifyMFACode, verifyMFASetup, } from './mfa.js';
|
|
16
|
+
export type { MfaCheckResult, MfaEnforcementOptions, MfaErrorResponse, MfaRequest, MfaSession, MfaSessionUser, } from './mfa-enforcement.js';
|
|
17
|
+
export { requireMfa } from './mfa-enforcement.js';
|
|
15
18
|
export { buildAuthUrl, exchangeCode, fetchProviderUser, generateOAuthState, getLinkedProviders, linkOAuthAccount, type ProviderUser, type UpsertOAuthOptions, unlinkOAuthAccount, upsertOAuthUser, verifyOAuthState, } from './oauth.js';
|
|
16
19
|
export type { PasskeyConfig } from './passkey.js';
|
|
17
20
|
export { configurePasskey, countUserCredentials, deletePasskey, generateAuthenticationChallenge, generateRegistrationChallenge, listPasskeys, renamePasskey, resetPasskeyConfig, storePasskey, verifyAuthentication, verifyRegistration, } from './passkey.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.js
CHANGED
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
* Server-side authentication functions for Next.js and TanStack Start.
|
|
5
5
|
* Inspired by Better Auth and Neon Auth patterns.
|
|
6
6
|
*/
|
|
7
|
+
// Audit bridge
|
|
8
|
+
export { auditAccountLocked, auditLoginFailure, auditLoginSuccess, auditMfaDisabled, auditMfaEnabled, auditPasswordChange, auditPasswordReset, auditSessionRevoked, } from './audit-bridge.js';
|
|
7
9
|
export { isSignupAllowed, signIn, signUp } from './auth.js';
|
|
8
10
|
export { clearFailedAttempts, configureBruteForce, getFailedAttemptCount, isAccountLocked, recordFailedAttempt, resetBruteForceConfig, } from './brute-force.js';
|
|
9
11
|
export { AuthError, AuthenticationError, DatabaseError, OAuthAccountConflictError, SessionError, TokenError, } from './errors.js';
|
|
10
12
|
export { configureMagicLink, createMagicLink, resetMagicLinkConfig, verifyMagicLink, } from './magic-link.js';
|
|
11
13
|
export { configureMFA, disableMFA, initiateMFASetup, isMFAEnabled, regenerateBackupCodes, resetMFAConfig, verifyBackupCode, verifyMFACode, verifyMFASetup, } from './mfa.js';
|
|
14
|
+
export { requireMfa } from './mfa-enforcement.js';
|
|
12
15
|
export { buildAuthUrl, exchangeCode, fetchProviderUser, generateOAuthState, getLinkedProviders, linkOAuthAccount, unlinkOAuthAccount, upsertOAuthUser, verifyOAuthState, } from './oauth.js';
|
|
13
16
|
export { configurePasskey, countUserCredentials, deletePasskey, generateAuthenticationChallenge, generateRegistrationChallenge, listPasskeys, renamePasskey, resetPasskeyConfig, storePasskey, verifyAuthentication, verifyRegistration, } from './passkey.js';
|
|
14
17
|
export { changePassword, generatePasswordResetToken, invalidatePasswordResetToken, resetPasswordWithToken, validatePasswordResetToken, } from './password-reset.js';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MFA Enforcement Middleware
|
|
3
|
+
*
|
|
4
|
+
* Provides a middleware factory that enforces Multi-Factor Authentication
|
|
5
|
+
* for specific roles and sensitive operations. SOC2 6.2 compliant.
|
|
6
|
+
*/
|
|
7
|
+
/** Options for the MFA enforcement middleware. */
|
|
8
|
+
export interface MfaEnforcementOptions {
|
|
9
|
+
/** Roles that require MFA to be enabled (default: ['admin']). */
|
|
10
|
+
roles?: string[];
|
|
11
|
+
/** Operations that require MFA (e.g. 'delete_user', 'change_role'). */
|
|
12
|
+
operations?: string[];
|
|
13
|
+
}
|
|
14
|
+
/** Shape of a session user as expected by the middleware. */
|
|
15
|
+
export interface MfaSessionUser {
|
|
16
|
+
/** User ID. */
|
|
17
|
+
id: string;
|
|
18
|
+
/** User's role. */
|
|
19
|
+
role: string;
|
|
20
|
+
/** Whether MFA is enabled on the account. */
|
|
21
|
+
mfaEnabled: boolean;
|
|
22
|
+
/** Whether MFA was verified in this session. */
|
|
23
|
+
mfaVerified?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Shape of the session object the middleware reads from. */
|
|
26
|
+
export interface MfaSession {
|
|
27
|
+
user: MfaSessionUser;
|
|
28
|
+
}
|
|
29
|
+
/** Request shape expected by the middleware. */
|
|
30
|
+
export interface MfaRequest {
|
|
31
|
+
/** The current session, if any. */
|
|
32
|
+
session?: MfaSession | null;
|
|
33
|
+
/** The operation being performed (matched against `options.operations`). */
|
|
34
|
+
operation?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Standard JSON error body returned when MFA is not satisfied. */
|
|
37
|
+
export interface MfaErrorResponse {
|
|
38
|
+
error: string;
|
|
39
|
+
code: 'MFA_REQUIRED' | 'MFA_VERIFY_REQUIRED';
|
|
40
|
+
}
|
|
41
|
+
/** Result of the MFA enforcement check. */
|
|
42
|
+
export interface MfaCheckResult {
|
|
43
|
+
/** Whether the request may proceed. */
|
|
44
|
+
allowed: boolean;
|
|
45
|
+
/** HTTP status code (403 when blocked, undefined when allowed). */
|
|
46
|
+
status?: number;
|
|
47
|
+
/** Error body (present only when blocked). */
|
|
48
|
+
body?: MfaErrorResponse;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create an MFA enforcement checker.
|
|
52
|
+
*
|
|
53
|
+
* Returns a function that inspects a request's session and determines
|
|
54
|
+
* whether MFA requirements are satisfied. Consumers are responsible
|
|
55
|
+
* for translating the result into their framework's response format
|
|
56
|
+
* (Hono, Express, Next.js, etc.).
|
|
57
|
+
*
|
|
58
|
+
* @param options - Roles and operations that require MFA
|
|
59
|
+
* @returns A check function that evaluates MFA requirements
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const checkMfa = requireMfa({ roles: ['admin'], operations: ['delete_user'] });
|
|
64
|
+
*
|
|
65
|
+
* // In a Hono route:
|
|
66
|
+
* app.delete('/users/:id', async (c) => {
|
|
67
|
+
* const result = checkMfa({ session: c.get('session'), operation: 'delete_user' });
|
|
68
|
+
* if (!result.allowed) {
|
|
69
|
+
* return c.json(result.body, result.status);
|
|
70
|
+
* }
|
|
71
|
+
* // proceed...
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function requireMfa(options?: MfaEnforcementOptions): (request: MfaRequest) => MfaCheckResult;
|
|
76
|
+
//# sourceMappingURL=mfa-enforcement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mfa-enforcement.d.ts","sourceRoot":"","sources":["../../src/server/mfa-enforcement.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,eAAe;IACf,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,gDAAgD;AAChD,MAAM,WAAW,UAAU;IACzB,mCAAmC;IACnC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,GAAG,qBAAqB,CAAC;CAC9C;AAED,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAQD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,UAAU,CACxB,OAAO,GAAE,qBAA0B,GAClC,CAAC,OAAO,EAAE,UAAU,KAAK,cAAc,CAgDzC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MFA Enforcement Middleware
|
|
3
|
+
*
|
|
4
|
+
* Provides a middleware factory that enforces Multi-Factor Authentication
|
|
5
|
+
* for specific roles and sensitive operations. SOC2 6.2 compliant.
|
|
6
|
+
*/
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Middleware
|
|
9
|
+
// =============================================================================
|
|
10
|
+
const DEFAULT_ROLES = ['admin'];
|
|
11
|
+
/**
|
|
12
|
+
* Create an MFA enforcement checker.
|
|
13
|
+
*
|
|
14
|
+
* Returns a function that inspects a request's session and determines
|
|
15
|
+
* whether MFA requirements are satisfied. Consumers are responsible
|
|
16
|
+
* for translating the result into their framework's response format
|
|
17
|
+
* (Hono, Express, Next.js, etc.).
|
|
18
|
+
*
|
|
19
|
+
* @param options - Roles and operations that require MFA
|
|
20
|
+
* @returns A check function that evaluates MFA requirements
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const checkMfa = requireMfa({ roles: ['admin'], operations: ['delete_user'] });
|
|
25
|
+
*
|
|
26
|
+
* // In a Hono route:
|
|
27
|
+
* app.delete('/users/:id', async (c) => {
|
|
28
|
+
* const result = checkMfa({ session: c.get('session'), operation: 'delete_user' });
|
|
29
|
+
* if (!result.allowed) {
|
|
30
|
+
* return c.json(result.body, result.status);
|
|
31
|
+
* }
|
|
32
|
+
* // proceed...
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function requireMfa(options = {}) {
|
|
37
|
+
const requiredRoles = options.roles ?? DEFAULT_ROLES;
|
|
38
|
+
const requiredOperations = options.operations ?? [];
|
|
39
|
+
return (request) => {
|
|
40
|
+
const session = request.session;
|
|
41
|
+
if (!session) {
|
|
42
|
+
// No session — nothing to enforce (auth middleware handles this)
|
|
43
|
+
return { allowed: true };
|
|
44
|
+
}
|
|
45
|
+
const user = session.user;
|
|
46
|
+
// Determine if MFA is required for this request
|
|
47
|
+
const roleRequiresMfa = requiredRoles.includes(user.role);
|
|
48
|
+
const operationRequiresMfa = request.operation !== undefined && requiredOperations.includes(request.operation);
|
|
49
|
+
if (!(roleRequiresMfa || operationRequiresMfa)) {
|
|
50
|
+
return { allowed: true };
|
|
51
|
+
}
|
|
52
|
+
// MFA is required but not enabled on the account
|
|
53
|
+
if (!user.mfaEnabled) {
|
|
54
|
+
return {
|
|
55
|
+
allowed: false,
|
|
56
|
+
status: 403,
|
|
57
|
+
body: {
|
|
58
|
+
error: 'MFA required',
|
|
59
|
+
code: 'MFA_REQUIRED',
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// MFA is enabled but not verified in this session
|
|
64
|
+
if (!user.mfaVerified) {
|
|
65
|
+
return {
|
|
66
|
+
allowed: false,
|
|
67
|
+
status: 403,
|
|
68
|
+
body: {
|
|
69
|
+
error: 'MFA verification required',
|
|
70
|
+
code: 'MFA_VERIFY_REQUIRED',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { allowed: true };
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../../src/server/providers/vercel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../../src/server/providers/vercel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiCrF;AAED,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAiC1E"}
|
|
@@ -34,6 +34,12 @@ export async function exchangeCode(code, redirectUri) {
|
|
|
34
34
|
throw new Error(`Vercel token exchange failed: ${response.status}${detail ? ` — ${detail}` : ''}`);
|
|
35
35
|
}
|
|
36
36
|
const data = (await response.json());
|
|
37
|
+
if (data.error) {
|
|
38
|
+
throw new Error(`Vercel token exchange error: ${data.error}`);
|
|
39
|
+
}
|
|
40
|
+
if (!data.access_token || typeof data.access_token !== 'string') {
|
|
41
|
+
throw new Error('Vercel token exchange returned no access_token');
|
|
42
|
+
}
|
|
37
43
|
return data.access_token;
|
|
38
44
|
}
|
|
39
45
|
export async function fetchUser(accessToken) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revealui/auth",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Authentication system for RevealUI - database-backed sessions with Better Auth patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
"bcryptjs": "^3.0.3",
|
|
15
15
|
"drizzle-orm": "^0.45.2",
|
|
16
16
|
"zod": "^4.3.6",
|
|
17
|
-
"@revealui/config": "0.3.
|
|
18
|
-
"@revealui/contracts": "1.3.
|
|
19
|
-
"@revealui/core": "0.5.
|
|
20
|
-
"@revealui/db": "0.3.
|
|
17
|
+
"@revealui/config": "0.3.3",
|
|
18
|
+
"@revealui/contracts": "1.3.6",
|
|
19
|
+
"@revealui/core": "0.5.5",
|
|
20
|
+
"@revealui/db": "0.3.6",
|
|
21
|
+
"@revealui/security": "0.2.6"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@simplewebauthn/browser": "^13.3.0",
|
|
@@ -71,6 +72,11 @@
|
|
|
71
72
|
},
|
|
72
73
|
"type": "module",
|
|
73
74
|
"types": "./dist/index.d.ts",
|
|
75
|
+
"repository": {
|
|
76
|
+
"type": "git",
|
|
77
|
+
"url": "https://github.com/RevealUIStudio/revealui.git",
|
|
78
|
+
"directory": "packages/auth"
|
|
79
|
+
},
|
|
74
80
|
"scripts": {
|
|
75
81
|
"build": "tsc",
|
|
76
82
|
"clean": "rm -rf dist",
|