@revealui/auth 0.3.8 → 0.4.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/dist/server/auth.d.ts +2 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +15 -2
- package/dist/server/mfa.d.ts.map +1 -1
- package/dist/server/mfa.js +12 -5
- package/dist/server/oauth.d.ts +1 -1
- package/dist/server/oauth.js +2 -2
- package/dist/server/password-reset.d.ts.map +1 -1
- package/dist/server/password-reset.js +4 -1
- package/dist/server/providers/github.d.ts +6 -2
- package/dist/server/providers/github.d.ts.map +1 -1
- package/dist/server/providers/github.js +16 -11
- package/dist/server/providers/google.d.ts.map +1 -1
- package/dist/server/providers/google.js +6 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +14 -14
package/dist/server/auth.d.ts
CHANGED
|
@@ -22,7 +22,8 @@ export declare function signIn(email: string, password: string, options?: {
|
|
|
22
22
|
* Behavior:
|
|
23
23
|
* - If REVEALUI_SIGNUP_OPEN is 'true', all emails are allowed.
|
|
24
24
|
* - If REVEALUI_SIGNUP_WHITELIST is set (comma-separated emails), only listed emails pass.
|
|
25
|
-
* - If neither env var is set, signups are
|
|
25
|
+
* - If neither env var is set, signups are CLOSED (security default).
|
|
26
|
+
* Set REVEALUI_SIGNUP_OPEN=true explicitly to open registration.
|
|
26
27
|
*
|
|
27
28
|
* @param email - Lowercase, trimmed email to check
|
|
28
29
|
* @returns true if signup is allowed
|
|
@@ -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,CAiLvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBtD;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,CAgMvB"}
|
package/dist/server/auth.js
CHANGED
|
@@ -164,6 +164,15 @@ export async function signIn(email, password, options) {
|
|
|
164
164
|
error: 'Failed to create session',
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
|
+
// Check if password rotation is required (e.g., bootstrapped admin accounts)
|
|
168
|
+
if (user.mustRotatePassword) {
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
requiresPasswordRotation: true,
|
|
172
|
+
user,
|
|
173
|
+
sessionToken: token,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
167
176
|
return {
|
|
168
177
|
success: true,
|
|
169
178
|
user,
|
|
@@ -185,7 +194,8 @@ export async function signIn(email, password, options) {
|
|
|
185
194
|
* Behavior:
|
|
186
195
|
* - If REVEALUI_SIGNUP_OPEN is 'true', all emails are allowed.
|
|
187
196
|
* - If REVEALUI_SIGNUP_WHITELIST is set (comma-separated emails), only listed emails pass.
|
|
188
|
-
* - If neither env var is set, signups are
|
|
197
|
+
* - If neither env var is set, signups are CLOSED (security default).
|
|
198
|
+
* Set REVEALUI_SIGNUP_OPEN=true explicitly to open registration.
|
|
189
199
|
*
|
|
190
200
|
* @param email - Lowercase, trimmed email to check
|
|
191
201
|
* @returns true if signup is allowed
|
|
@@ -197,7 +207,10 @@ export function isSignupAllowed(email) {
|
|
|
197
207
|
}
|
|
198
208
|
const whitelist = process.env.REVEALUI_SIGNUP_WHITELIST;
|
|
199
209
|
if (!whitelist) {
|
|
200
|
-
|
|
210
|
+
// Default to closed — require explicit REVEALUI_SIGNUP_OPEN=true
|
|
211
|
+
// or a whitelist to allow signups. Prevents accidental open
|
|
212
|
+
// registration on new deployments.
|
|
213
|
+
return false;
|
|
201
214
|
}
|
|
202
215
|
const allowedEmails = whitelist
|
|
203
216
|
.split(',')
|
package/dist/server/mfa.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mfa.d.ts","sourceRoot":"","sources":["../../src/server/mfa.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"mfa.d.ts","sourceRoot":"","sources":["../../src/server/mfa.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAcH,MAAM,WAAW,SAAS;IACxB,sDAAsD;IACtD,eAAe,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD,wBAAgB,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,CAEhE;AAED,wBAAgB,cAAc,IAAI,IAAI,CAErC;AA2ED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA0C7F;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAyC/C;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkC/C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAuCxE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwBvE;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC;AAE1C;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgD/C;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUnE"}
|
package/dist/server/mfa.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
8
8
|
import { TwoFactorAuth } from '@revealui/core/security';
|
|
9
9
|
import { getClient } from '@revealui/db/client';
|
|
10
|
+
import { decryptFieldOrPassthrough, encryptField } from '@revealui/db/crypto';
|
|
10
11
|
import { users } from '@revealui/db/schema';
|
|
11
12
|
import bcrypt from 'bcryptjs';
|
|
12
13
|
import { eq } from 'drizzle-orm';
|
|
@@ -100,11 +101,13 @@ export async function initiateMFASetup(userId, email) {
|
|
|
100
101
|
const secret = TwoFactorAuth.generateSecret();
|
|
101
102
|
const { plaintext, hashed } = await generateBackupCodes();
|
|
102
103
|
const uri = buildProvisioningUri(secret, email);
|
|
103
|
-
//
|
|
104
|
+
// Encrypt TOTP secret before storage (AES-256-GCM, KEK from REVEALUI_KEK)
|
|
105
|
+
const encryptedSecret = encryptField(secret);
|
|
106
|
+
// Store encrypted secret and backup codes (MFA stays disabled until verified)
|
|
104
107
|
await db
|
|
105
108
|
.update(users)
|
|
106
109
|
.set({
|
|
107
|
-
mfaSecret:
|
|
110
|
+
mfaSecret: encryptedSecret,
|
|
108
111
|
mfaBackupCodes: hashed,
|
|
109
112
|
updatedAt: new Date(),
|
|
110
113
|
})
|
|
@@ -136,8 +139,10 @@ export async function verifyMFASetup(userId, code) {
|
|
|
136
139
|
if (!user.mfaSecret) {
|
|
137
140
|
return { success: false, error: 'MFA setup not initiated' };
|
|
138
141
|
}
|
|
139
|
-
//
|
|
140
|
-
const
|
|
142
|
+
// Decrypt TOTP secret (supports legacy plaintext for rolling migration)
|
|
143
|
+
const plainSecret = decryptFieldOrPassthrough(user.mfaSecret);
|
|
144
|
+
// Verify the TOTP code against the decrypted secret
|
|
145
|
+
const valid = TwoFactorAuth.verifyCode(plainSecret, code);
|
|
141
146
|
if (!valid) {
|
|
142
147
|
return { success: false, error: 'Invalid verification code' };
|
|
143
148
|
}
|
|
@@ -172,7 +177,9 @@ export async function verifyMFACode(userId, code) {
|
|
|
172
177
|
if (!(user?.mfaEnabled && user.mfaSecret)) {
|
|
173
178
|
return { success: false, error: 'MFA not enabled' };
|
|
174
179
|
}
|
|
175
|
-
|
|
180
|
+
// Decrypt TOTP secret (supports legacy plaintext for rolling migration)
|
|
181
|
+
const plainSecret = decryptFieldOrPassthrough(user.mfaSecret);
|
|
182
|
+
const matchedCounter = verifyCodeWithCounter(plainSecret, code);
|
|
176
183
|
if (matchedCounter === null) {
|
|
177
184
|
return { success: false, error: 'Invalid code' };
|
|
178
185
|
}
|
package/dist/server/oauth.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export interface UpsertOAuthOptions {
|
|
|
55
55
|
* 1. Look up oauth_accounts by (provider, providerUserId) → get userId
|
|
56
56
|
* 2. If found: refresh metadata + return user
|
|
57
57
|
* 3. If not found: check users by email → link if match (with consent) or throw
|
|
58
|
-
* 4. If no match: create new user (role: '
|
|
58
|
+
* 4. If no match: create new user (role: 'viewer', no password)
|
|
59
59
|
* 5. Insert oauth_accounts row
|
|
60
60
|
*/
|
|
61
61
|
export declare function upsertOAuthUser(provider: string, providerUser: ProviderUser, options?: UpsertOAuthOptions): Promise<User>;
|
package/dist/server/oauth.js
CHANGED
|
@@ -153,7 +153,7 @@ export async function fetchProviderUser(provider, accessToken) {
|
|
|
153
153
|
* 1. Look up oauth_accounts by (provider, providerUserId) → get userId
|
|
154
154
|
* 2. If found: refresh metadata + return user
|
|
155
155
|
* 3. If not found: check users by email → link if match (with consent) or throw
|
|
156
|
-
* 4. If no match: create new user (role: '
|
|
156
|
+
* 4. If no match: create new user (role: 'viewer', no password)
|
|
157
157
|
* 5. Insert oauth_accounts row
|
|
158
158
|
*/
|
|
159
159
|
export async function upsertOAuthUser(provider, providerUser, options) {
|
|
@@ -227,7 +227,7 @@ export async function upsertOAuthUser(provider, providerUser, options) {
|
|
|
227
227
|
email: providerUser.email,
|
|
228
228
|
avatarUrl: providerUser.avatarUrl,
|
|
229
229
|
password: null,
|
|
230
|
-
role: '
|
|
230
|
+
role: 'viewer',
|
|
231
231
|
status: 'active',
|
|
232
232
|
});
|
|
233
233
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-reset.d.ts","sourceRoot":"","sources":["../../src/server/password-reset.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuBD;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwE5F;AAED;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqCxB;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,mBAAmB,CAAC,CAuF9B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,oBAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"password-reset.d.ts","sourceRoot":"","sources":["../../src/server/password-reset.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuBD;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwE5F;AAED;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqCxB;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,mBAAmB,CAAC,CAuF9B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAoD/B;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqChG"}
|
|
@@ -240,7 +240,10 @@ export async function changePassword(userId, currentPassword, newPassword, curre
|
|
|
240
240
|
return { success: false, error: 'Current password is incorrect.' };
|
|
241
241
|
}
|
|
242
242
|
const newHash = await bcrypt.hash(newPassword, 12);
|
|
243
|
-
await db
|
|
243
|
+
await db
|
|
244
|
+
.update(users)
|
|
245
|
+
.set({ password: newHash, mustRotatePassword: false })
|
|
246
|
+
.where(eq(users.id, userId));
|
|
244
247
|
// Invalidate all other sessions so stolen/compromised sessions cannot persist.
|
|
245
248
|
// This mirrors resetPasswordWithToken which deletes ALL sessions. Here we keep
|
|
246
249
|
// the current session so the user stays logged in after changing their password.
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
* Uses native fetch - no additional npm dependencies.
|
|
5
5
|
* Scopes: read:user user:email
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The email returned by GET /user is NOT verified — it's any value the
|
|
8
|
+
* user typed into Settings → Public profile email. We always query
|
|
9
|
+
* /user/emails and pick the `primary && verified` entry. See revealui#832
|
|
10
|
+
* for the account-takeover vector that motivated this: an attacker could
|
|
11
|
+
* set their public profile email to a victim's address and create a
|
|
12
|
+
* RevealUI account under that email before the victim signed up.
|
|
9
13
|
*/
|
|
10
14
|
import type { ProviderUser } from '../oauth.js';
|
|
11
15
|
export declare function buildAuthUrl(clientId: string, redirectUri: string, state: string, codeChallenge?: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../../src/server/providers/github.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../../src/server/providers/github.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAWR;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAwCjB;AAED,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAkD1E"}
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
* Uses native fetch - no additional npm dependencies.
|
|
5
5
|
* Scopes: read:user user:email
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The email returned by GET /user is NOT verified — it's any value the
|
|
8
|
+
* user typed into Settings → Public profile email. We always query
|
|
9
|
+
* /user/emails and pick the `primary && verified` entry. See revealui#832
|
|
10
|
+
* for the account-takeover vector that motivated this: an attacker could
|
|
11
|
+
* set their public profile email to a victim's address and create a
|
|
12
|
+
* RevealUI account under that email before the victim signed up.
|
|
9
13
|
*/
|
|
10
14
|
export function buildAuthUrl(clientId, redirectUri, state, codeChallenge) {
|
|
11
15
|
const url = new URL('https://github.com/login/oauth/authorize');
|
|
@@ -75,15 +79,16 @@ export async function fetchUser(accessToken) {
|
|
|
75
79
|
throw new Error(`GitHub user fetch failed: ${userResponse.status}${detail ? ` - ${detail}` : ''}`);
|
|
76
80
|
}
|
|
77
81
|
const user = (await userResponse.json());
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
// Per revealui#832: always query /user/emails and require primary +
|
|
83
|
+
// verified. We do NOT trust user.email from /user — that field is
|
|
84
|
+
// user-editable in GitHub's Settings → Public profile and is not
|
|
85
|
+
// verified by GitHub.
|
|
86
|
+
let email = null;
|
|
87
|
+
const emailsResponse = await fetch('https://api.github.com/user/emails', { headers });
|
|
88
|
+
if (emailsResponse.ok) {
|
|
89
|
+
const emails = (await emailsResponse.json());
|
|
90
|
+
const primary = emails.find((e) => e.primary && e.verified);
|
|
91
|
+
email = primary?.email ?? null;
|
|
87
92
|
}
|
|
88
93
|
return {
|
|
89
94
|
id: String(user.id),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../../src/server/providers/google.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAaR;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAmCjB;AAED,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../../src/server/providers/google.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAaR;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAmCjB;AAED,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAqC1E"}
|
|
@@ -69,7 +69,12 @@ export async function fetchUser(accessToken) {
|
|
|
69
69
|
const data = (await response.json());
|
|
70
70
|
return {
|
|
71
71
|
id: data.sub,
|
|
72
|
-
|
|
72
|
+
// Per revealui#832: require email_verified === true. Google's userinfo
|
|
73
|
+
// includes the email_verified claim; an unverified email is just
|
|
74
|
+
// whatever the user typed during Google signup and is not proof of
|
|
75
|
+
// ownership. Returning null forces the callback's signup gate to
|
|
76
|
+
// reject the request (verified_email_required).
|
|
77
|
+
email: data.email_verified === true ? (data.email ?? null) : null,
|
|
73
78
|
name: data.name ?? 'Google User',
|
|
74
79
|
avatarUrl: data.picture ?? null,
|
|
75
80
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface User {
|
|
|
17
17
|
email: string | null;
|
|
18
18
|
avatarUrl: string | null;
|
|
19
19
|
password: string | null;
|
|
20
|
+
mustRotatePassword?: boolean;
|
|
20
21
|
role: string;
|
|
21
22
|
status: string;
|
|
22
23
|
agentModel: string | null;
|
|
@@ -58,12 +59,20 @@ export interface AuthSession {
|
|
|
58
59
|
export type SignInResult = {
|
|
59
60
|
success: true;
|
|
60
61
|
requiresMfa?: false;
|
|
62
|
+
requiresPasswordRotation?: false;
|
|
61
63
|
user: User;
|
|
62
64
|
sessionToken: string;
|
|
63
65
|
} | {
|
|
64
66
|
success: true;
|
|
65
67
|
requiresMfa: true;
|
|
68
|
+
requiresPasswordRotation?: false;
|
|
66
69
|
mfaUserId: string;
|
|
70
|
+
} | {
|
|
71
|
+
success: true;
|
|
72
|
+
requiresMfa?: false;
|
|
73
|
+
requiresPasswordRotation: true;
|
|
74
|
+
user: User;
|
|
75
|
+
sessionToken: string;
|
|
67
76
|
} | {
|
|
68
77
|
success: false;
|
|
69
78
|
reason: 'invalid_credentials' | 'account_locked' | 'rate_limited' | 'database_error' | 'session_error' | 'email_not_verified' | 'unexpected_error';
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,IAAI,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,0GAA0G;AAC1G,MAAM,MAAM,YAAY,GACpB;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,IAAI,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,0GAA0G;AAC1G,MAAM,MAAM,YAAY,GACpB;IACE,OAAO,EAAE,IAAI,CAAC;IACd,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,wBAAwB,CAAC,EAAE,KAAK,CAAC;IACjC,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;CACtB,GACD;IACE,OAAO,EAAE,IAAI,CAAC;IACd,WAAW,EAAE,IAAI,CAAC;IAClB,wBAAwB,CAAC,EAAE,KAAK,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB,GACD;IACE,OAAO,EAAE,IAAI,CAAC;IACd,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,wBAAwB,EAAE,IAAI,CAAC;IAC/B,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;CACtB,GACD;IACE,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EACF,qBAAqB,GACrB,gBAAgB,GAChB,cAAc,GACd,gBAAgB,GAChB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revealui/auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Database-backed session auth for Hono and Next.js — bcrypt, OAuth, brute-force protection, rate limiting, password reset. Ships with RevealUI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
7
7
|
"authentication",
|
|
@@ -13,23 +13,23 @@
|
|
|
13
13
|
"@simplewebauthn/server": "^13.3.0",
|
|
14
14
|
"bcryptjs": "^3.0.3",
|
|
15
15
|
"drizzle-orm": "^0.45.2",
|
|
16
|
-
"zod": "^4.3
|
|
17
|
-
"@revealui/config": "0.
|
|
18
|
-
"@revealui/contracts": "
|
|
19
|
-
"@revealui/core": "0.
|
|
20
|
-
"@revealui/db": "0.
|
|
21
|
-
"@revealui/security": "0.
|
|
16
|
+
"zod": "^4.4.3",
|
|
17
|
+
"@revealui/config": "0.4.1",
|
|
18
|
+
"@revealui/contracts": "0.5.0",
|
|
19
|
+
"@revealui/core": "0.7.0",
|
|
20
|
+
"@revealui/db": "0.5.0",
|
|
21
|
+
"@revealui/security": "0.3.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@simplewebauthn/browser": "^13.3.0",
|
|
25
25
|
"@testing-library/react": "^16.3.2",
|
|
26
|
-
"@types/node": "^25.
|
|
26
|
+
"@types/node": "^25.6.0",
|
|
27
27
|
"@types/react": "^19.2.14",
|
|
28
|
-
"@vitest/coverage-v8": "^4.1.
|
|
29
|
-
"happy-dom": "^20.
|
|
30
|
-
"react": "^19.2.
|
|
31
|
-
"typescript": "^6.0.
|
|
32
|
-
"vitest": "^4.1.
|
|
28
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
29
|
+
"happy-dom": "^20.9.0",
|
|
30
|
+
"react": "^19.2.6",
|
|
31
|
+
"typescript": "^6.0.3",
|
|
32
|
+
"vitest": "^4.1.5",
|
|
33
33
|
"@revealui/dev": "0.1.0"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|