@revealui/auth 0.3.4 → 0.3.6
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 +14 -0
- 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/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/password-validation.d.ts.map +1 -1
- package/dist/server/password-validation.js +12 -7
- package/dist/server/providers/vercel.d.ts.map +1 -1
- package/dist/server/providers/vercel.js +6 -0
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -90,6 +90,20 @@ pnpm typecheck
|
|
|
90
90
|
pnpm test
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
## When to Use This
|
|
94
|
+
|
|
95
|
+
- You need session-based auth with database-backed sessions for a RevealUI app
|
|
96
|
+
- You want built-in brute force protection and rate limiting without external services
|
|
97
|
+
- You need React hooks for client-side session management (`useSession`, `useSignIn`, `useSignOut`)
|
|
98
|
+
- **Not** for OAuth-only flows — use a dedicated OAuth provider and wire tokens through this package
|
|
99
|
+
- **Not** for stateless JWT auth — this package uses database sessions by design
|
|
100
|
+
|
|
101
|
+
## JOSHUA Alignment
|
|
102
|
+
|
|
103
|
+
- **Sovereign**: Sessions live in your PostgreSQL database, not a third-party auth service
|
|
104
|
+
- **Hermetic**: HTTP-only, SameSite cookies and SHA-256 token hashing prevent cross-boundary leaks
|
|
105
|
+
- **Justifiable**: Every security layer (bcrypt, progressive lockout, rate limiting) exists because the threat model demands it
|
|
106
|
+
|
|
93
107
|
## Related
|
|
94
108
|
|
|
95
109
|
- [Core Package](../core/README.md) — CMS engine (uses auth for access control)
|
|
@@ -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
|
+
}
|
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":"password-validation.d.ts","sourceRoot":"","sources":["../../src/server/password-validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"password-validation.d.ts","sourceRoot":"","sources":["../../src/server/password-validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,wBAAwB,CA2BnF;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1E"}
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Password strength validation and requirements.
|
|
5
5
|
*/
|
|
6
|
+
/** Check if any character in the string falls within the given char code range (inclusive) */
|
|
7
|
+
function hasCharInRange(str, low, high) {
|
|
8
|
+
for (let i = 0; i < str.length; i++) {
|
|
9
|
+
const code = str.charCodeAt(i);
|
|
10
|
+
if (code >= low && code <= high)
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
16
|
* Validates password strength
|
|
8
17
|
*
|
|
@@ -17,19 +26,15 @@ export function validatePasswordStrength(password) {
|
|
|
17
26
|
if (password.length > 128) {
|
|
18
27
|
errors.push('Password must be less than 128 characters');
|
|
19
28
|
}
|
|
20
|
-
if (
|
|
29
|
+
if (!hasCharInRange(password, 97, 122)) {
|
|
21
30
|
errors.push('Password must contain at least one lowercase letter');
|
|
22
31
|
}
|
|
23
|
-
if (
|
|
32
|
+
if (!hasCharInRange(password, 65, 90)) {
|
|
24
33
|
errors.push('Password must contain at least one uppercase letter');
|
|
25
34
|
}
|
|
26
|
-
if (
|
|
35
|
+
if (!hasCharInRange(password, 48, 57)) {
|
|
27
36
|
errors.push('Password must contain at least one number');
|
|
28
37
|
}
|
|
29
|
-
// Optional: special characters (not too strict)
|
|
30
|
-
// if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
|
|
31
|
-
// errors.push('Password must contain at least one special character')
|
|
32
|
-
// }
|
|
33
38
|
return {
|
|
34
39
|
valid: errors.length === 0,
|
|
35
40
|
errors,
|
|
@@ -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.6",
|
|
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/
|
|
18
|
-
"@revealui/
|
|
19
|
-
"@revealui/
|
|
20
|
-
"@revealui/
|
|
17
|
+
"@revealui/config": "0.3.2",
|
|
18
|
+
"@revealui/contracts": "1.3.5",
|
|
19
|
+
"@revealui/core": "0.5.4",
|
|
20
|
+
"@revealui/db": "0.3.5",
|
|
21
|
+
"@revealui/security": "0.2.5"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@simplewebauthn/browser": "^13.3.0",
|