@payez/next-mvp 4.10.0 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/dist/api-handlers/account/recovery/forward.js +2 -1
- package/dist/api-handlers/account/recovery/initiate.js +2 -1
- package/dist/api-handlers/account/reset-password.js +2 -1
- package/dist/api-handlers/account/send-code.js +2 -1
- package/dist/api-handlers/account/update-phone.js +2 -1
- package/dist/api-handlers/account/validate-password.js +2 -1
- package/dist/api-handlers/account/verify-email.js +2 -1
- package/dist/api-handlers/account/verify-sms.js +2 -1
- package/dist/api-handlers/anon/preferences.js +3 -2
- package/dist/api-handlers/auth/credential-login-email-code.js +3 -2
- package/dist/api-handlers/auth/credential-login.js +6 -5
- package/dist/api-handlers/auth/refresh.d.ts +17 -58
- package/dist/api-handlers/auth/refresh.js +73 -773
- package/dist/api-handlers/auth/signout.js +43 -6
- package/dist/api-handlers/mfa/backup-code-verify.js +2 -1
- package/dist/api-handlers/mfa/totp-verify.js +2 -1
- package/dist/api-handlers/session/refresh-viability.js +3 -2
- package/dist/api-handlers/session/viability.js +2 -1
- package/dist/auth/better-auth.js +7 -6
- package/dist/auth/idp-oauth.js +4 -2
- package/dist/auth/utils/idp-client.js +5 -4
- package/dist/auth/utils/token-utils.d.ts +1 -1
- package/dist/auth/utils/token-utils.js +7 -5
- package/dist/config/logger.js +2 -1
- package/dist/config/logging-config.js +4 -3
- package/dist/config/vibe-log-transport.js +2 -1
- package/dist/lib/api-handler.js +4 -3
- package/dist/lib/ensure-fresh-access-token.d.ts +24 -1
- package/dist/lib/ensure-fresh-access-token.js +195 -28
- package/dist/lib/geolocation.js +5 -22
- package/dist/lib/idp-client-config.js +4 -3
- package/dist/lib/idp-fetch.d.ts +11 -0
- package/dist/lib/idp-fetch.js +6 -3
- package/dist/lib/idp-logout.d.ts +10 -0
- package/dist/lib/idp-logout.js +73 -1
- package/dist/lib/jwt-decode-client.js +2 -1
- package/dist/lib/jwt-decode.js +2 -1
- package/dist/lib/log-safe.d.ts +19 -0
- package/dist/lib/log-safe.js +32 -0
- package/dist/lib/redis.js +2 -1
- package/dist/lib/refresh-in-doubt.d.ts +31 -0
- package/dist/lib/refresh-in-doubt.js +81 -0
- package/dist/lib/refresh-token-validator.js +2 -1
- package/dist/lib/refresh-window.d.ts +16 -0
- package/dist/lib/refresh-window.js +19 -0
- package/dist/lib/safe-error-response.d.ts +5 -1
- package/dist/lib/safe-error-response.js +7 -2
- package/dist/lib/safe-trace-id.d.ts +11 -0
- package/dist/lib/safe-trace-id.js +23 -0
- package/dist/lib/session-store.js +16 -5
- package/dist/lib/site-logger.d.ts +2 -8
- package/dist/lib/site-logger.js +11 -5
- package/dist/lib/standardized-client-api.js +3 -2
- package/dist/lib/token-lifecycle.js +6 -4
- package/dist/middleware/create-middleware.js +2 -1
- package/dist/middleware/rbac-check.d.ts +0 -13
- package/dist/middleware/rbac-check.js +6 -5
- package/dist/models/SessionModel.d.ts +7 -0
- package/dist/routes/auth/logout.js +6 -2
- package/dist/routes/auth/refresh.d.ts +1 -15
- package/dist/routes/auth/settings.js +2 -1
- package/dist/server/auth-guard.js +4 -3
- package/dist/server/auth.d.ts +1 -1
- package/dist/server/auth.js +5 -9
- package/dist/server/decode-session.js +3 -2
- package/dist/server/with-auth.js +2 -1
- package/dist/stores/authStore.js +3 -2
- package/package.json +1 -1
- package/scripts/verify-release-provenance.mjs +1 -0
- package/src/api-handlers/account/recovery/forward.ts +2 -1
- package/src/api-handlers/account/recovery/initiate.ts +2 -1
- package/src/api-handlers/account/reset-password.ts +2 -1
- package/src/api-handlers/account/send-code.ts +2 -1
- package/src/api-handlers/account/update-phone.ts +2 -1
- package/src/api-handlers/account/validate-password.ts +2 -1
- package/src/api-handlers/account/verify-email.ts +2 -1
- package/src/api-handlers/account/verify-sms.ts +2 -1
- package/src/api-handlers/anon/preferences.ts +3 -2
- package/src/api-handlers/auth/credential-login-email-code.ts +3 -2
- package/src/api-handlers/auth/credential-login.ts +6 -5
- package/src/api-handlers/auth/refresh.ts +74 -813
- package/src/api-handlers/auth/signout.ts +54 -7
- package/src/api-handlers/mfa/backup-code-verify.ts +2 -1
- package/src/api-handlers/mfa/totp-verify.ts +2 -1
- package/src/api-handlers/session/refresh-viability.ts +3 -2
- package/src/api-handlers/session/viability.ts +2 -1
- package/src/auth/better-auth.ts +7 -6
- package/src/auth/idp-oauth.ts +7 -2
- package/src/auth/utils/idp-client.ts +5 -4
- package/src/auth/utils/token-utils.ts +7 -5
- package/src/config/logger.ts +2 -1
- package/src/config/logging-config.ts +4 -3
- package/src/config/vibe-log-transport.ts +2 -1
- package/src/lib/api-handler.ts +4 -3
- package/src/lib/ensure-fresh-access-token.ts +214 -29
- package/src/lib/geolocation.ts +5 -4
- package/src/lib/idp-client-config.ts +4 -3
- package/src/lib/idp-fetch.ts +14 -3
- package/src/lib/idp-logout.ts +36 -1
- package/src/lib/jwt-decode-client.ts +3 -1
- package/src/lib/jwt-decode.ts +2 -1
- package/src/lib/log-safe.ts +30 -0
- package/src/lib/redis.ts +2 -1
- package/src/lib/refresh-in-doubt.ts +79 -0
- package/src/lib/refresh-token-validator.ts +2 -1
- package/src/lib/refresh-window.ts +16 -0
- package/src/lib/safe-error-response.ts +7 -2
- package/src/lib/safe-trace-id.ts +21 -0
- package/src/lib/session-store.ts +17 -4
- package/src/lib/site-logger.ts +13 -7
- package/src/lib/standardized-client-api.ts +3 -2
- package/src/lib/token-lifecycle.ts +6 -4
- package/src/middleware/create-middleware.ts +2 -1
- package/src/middleware/rbac-check.ts +2 -1
- package/src/models/SessionModel.ts +7 -0
- package/src/routes/auth/logout.ts +9 -2
- package/src/routes/auth/settings.ts +2 -1
- package/src/server/auth-guard.ts +4 -3
- package/src/server/auth.ts +6 -11
- package/src/server/decode-session.ts +3 -2
- package/src/server/with-auth.ts +2 -1
- package/src/stores/authStore.ts +3 -2
|
@@ -15,6 +15,9 @@ exports.createSignoutHandler = createSignoutHandler;
|
|
|
15
15
|
const server_1 = require("next/server");
|
|
16
16
|
const headers_1 = require("next/headers");
|
|
17
17
|
const session_store_1 = require("../../lib/session-store");
|
|
18
|
+
const idp_logout_1 = require("../../lib/idp-logout");
|
|
19
|
+
const log_safe_1 = require("../../lib/log-safe");
|
|
20
|
+
const logout_1 = require("../../routes/auth/logout");
|
|
18
21
|
const auth_1 = require("../../server/auth");
|
|
19
22
|
const app_slug_1 = require("../../lib/app-slug");
|
|
20
23
|
const token_log_id_1 = require("../../lib/token-log-id");
|
|
@@ -130,9 +133,39 @@ function createSignoutHandler() {
|
|
|
130
133
|
console.warn('[SIGNOUT] Manual JWT decode failed to extract session token');
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
|
-
//
|
|
134
|
-
//
|
|
136
|
+
// GOV-023 (BAPert 76687): ending the session ends the IdP GRANT first, the same IdP-first path as routes/auth/logout. Before this, this
|
|
137
|
+
// handler only deleted the local record, so a consumer who mounted it left the IdP refresh token live until it expired.
|
|
138
|
+
// A failed or unresolved revoke is the same typed failure as a failed local delete (SESSION_NOT_ENDED + cause); the local delete still
|
|
139
|
+
// runs and the cookies are cleared either way.
|
|
140
|
+
let idpRevokeFailed = false;
|
|
141
|
+
let localDeleteFailed = false;
|
|
135
142
|
if (redisSessionToken) {
|
|
143
|
+
// Does the stored session hold an IdP refresh token? (revokeSessionAtIdp reads the canonical row, then the Better Auth record.)
|
|
144
|
+
let heldRefreshToken = false;
|
|
145
|
+
try {
|
|
146
|
+
const stored = (await (0, session_store_1.getSession)(redisSessionToken)) ?? (await (0, session_store_1.getBetterAuthSession)(redisSessionToken));
|
|
147
|
+
heldRefreshToken = !!stored?.idpRefreshToken;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// unknown: treated as "none held", so an unreadable store does not turn a clean sign-out into a failure on its own
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const outcome = await (0, idp_logout_1.revokeSessionAtIdp)(req);
|
|
154
|
+
// 'nothing-to-revoke' while a refresh token IS held means the helper could not resolve this session from the request (this
|
|
155
|
+
// handler also finds the session by decoding the cookie when Better Auth cannot): the grant is NOT ended, so say so.
|
|
156
|
+
if (outcome === 'nothing-to-revoke' && heldRefreshToken) {
|
|
157
|
+
idpRevokeFailed = true;
|
|
158
|
+
console.error('[SIGNOUT] IdP revoke could not resolve the session - the IdP refresh token is STILL LIVE');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (revokeError) {
|
|
162
|
+
idpRevokeFailed = true;
|
|
163
|
+
console.error(revokeError?.grantLive === true
|
|
164
|
+
? '[SIGNOUT] IdP revoke failed - the IdP refresh token is STILL LIVE:'
|
|
165
|
+
: '[SIGNOUT] IdP revoke could not be confirmed (the grant may already be dead):', (0, log_safe_1.errForLog)(revokeError));
|
|
166
|
+
}
|
|
167
|
+
// Delete the session from EVERY store (181922): canonical + BA secondary.
|
|
168
|
+
// Canonical-only deletion let the ba: record resurrect the session.
|
|
136
169
|
try {
|
|
137
170
|
await (0, session_store_1.deleteSessionEverywhere)(redisSessionToken);
|
|
138
171
|
console.info('[SIGNOUT] Redis session cleanup successful', {
|
|
@@ -140,21 +173,25 @@ function createSignoutHandler() {
|
|
|
140
173
|
});
|
|
141
174
|
}
|
|
142
175
|
catch (sessionError) {
|
|
143
|
-
|
|
144
|
-
console.error('[SIGNOUT] Redis session cleanup failed
|
|
176
|
+
localDeleteFailed = true;
|
|
177
|
+
console.error('[SIGNOUT] Redis session cleanup failed - the server session is STILL LIVE:', (0, log_safe_1.errForLog)(sessionError));
|
|
145
178
|
}
|
|
146
179
|
}
|
|
147
180
|
else {
|
|
148
181
|
console.warn('[SIGNOUT] No Redis session token (UUID) extracted from cookie; skipping Redis deletion');
|
|
149
182
|
}
|
|
150
|
-
// Build response
|
|
183
|
+
// Build response. A sign-out that did not end the session must not say it did (same typed answer as routes/auth/logout).
|
|
151
184
|
const responseData = {
|
|
152
185
|
success: true,
|
|
153
186
|
message: sessionToken ? 'Session deleted successfully' : 'No active session found',
|
|
154
187
|
sessionDeleted: !!sessionToken,
|
|
155
188
|
chunkCookiesDeleted: chunkCookies.length
|
|
156
189
|
};
|
|
157
|
-
const response =
|
|
190
|
+
const response = (localDeleteFailed || idpRevokeFailed)
|
|
191
|
+
? server_1.NextResponse.json(localDeleteFailed
|
|
192
|
+
? { success: false, code: logout_1.SESSION_NOT_ENDED_CODE, cause: 'local_delete_failed', message: logout_1.SESSION_NOT_ENDED_LOCAL_MESSAGE }
|
|
193
|
+
: { success: false, code: logout_1.SESSION_NOT_ENDED_CODE, cause: 'idp_unconfirmed', message: logout_1.SESSION_NOT_ENDED_IDP_MESSAGE }, { status: 500 })
|
|
194
|
+
: server_1.NextResponse.json(responseData);
|
|
158
195
|
// Always clear cookies, regardless of success/failure (using app-slug prefixed names)
|
|
159
196
|
try {
|
|
160
197
|
response.cookies.delete(sessionCookieName);
|
|
@@ -62,6 +62,7 @@ const test_aware_get_token_1 = require("../../lib/test-aware-get-token");
|
|
|
62
62
|
const session_store_1 = require("../../lib/session-store");
|
|
63
63
|
const jwt_decode_1 = require("../../lib/jwt-decode");
|
|
64
64
|
const two_factor_verify_failure_1 = require("../../lib/two-factor-verify-failure");
|
|
65
|
+
const log_safe_1 = require("../../lib/log-safe");
|
|
65
66
|
async function POST(req) {
|
|
66
67
|
try {
|
|
67
68
|
const body = await req.json();
|
|
@@ -141,7 +142,7 @@ async function POST(req) {
|
|
|
141
142
|
}, { status: 200 });
|
|
142
143
|
}
|
|
143
144
|
catch (error) {
|
|
144
|
-
console.error('[VERIFY_BACKUP_CODE] Error:', error);
|
|
145
|
+
console.error('[VERIFY_BACKUP_CODE] Error:', (0, log_safe_1.errForLog)(error));
|
|
145
146
|
return server_1.NextResponse.json({
|
|
146
147
|
success: false,
|
|
147
148
|
error: 'Failed to verify backup code',
|
|
@@ -37,6 +37,7 @@ const test_aware_get_token_1 = require("../../lib/test-aware-get-token");
|
|
|
37
37
|
const session_store_1 = require("../../lib/session-store");
|
|
38
38
|
const jwt_decode_1 = require("../../lib/jwt-decode");
|
|
39
39
|
const two_factor_verify_failure_1 = require("../../lib/two-factor-verify-failure");
|
|
40
|
+
const log_safe_1 = require("../../lib/log-safe");
|
|
40
41
|
async function POST(req) {
|
|
41
42
|
try {
|
|
42
43
|
const body = await req.json();
|
|
@@ -141,7 +142,7 @@ async function POST(req) {
|
|
|
141
142
|
}, { status: 200 });
|
|
142
143
|
}
|
|
143
144
|
catch (error) {
|
|
144
|
-
console.error('[VERIFY_TOTP] Error:', error);
|
|
145
|
+
console.error('[VERIFY_TOTP] Error:', (0, log_safe_1.errForLog)(error));
|
|
145
146
|
return server_1.NextResponse.json({
|
|
146
147
|
success: false,
|
|
147
148
|
error: 'Failed to verify code',
|
|
@@ -7,6 +7,7 @@ const refresh_token_validator_1 = require("../../lib/refresh-token-validator");
|
|
|
7
7
|
const test_aware_get_token_1 = require("../../lib/test-aware-get-token");
|
|
8
8
|
const logger_1 = require("../../config/logger");
|
|
9
9
|
const token_log_id_1 = require("../../lib/token-log-id");
|
|
10
|
+
const log_safe_1 = require("../../lib/log-safe");
|
|
10
11
|
const auth_1 = require("../../server/auth");
|
|
11
12
|
async function GET(req) {
|
|
12
13
|
try {
|
|
@@ -53,8 +54,8 @@ async function GET(req) {
|
|
|
53
54
|
}
|
|
54
55
|
catch (error) {
|
|
55
56
|
const correlationId = globalThis.crypto.randomUUID();
|
|
56
|
-
logger_1.logger.error('[REFRESH-VIABILITY] Error checking refresh viability', { correlationId, error:
|
|
57
|
-
// PAY-1438: the
|
|
57
|
+
logger_1.logger.error('[REFRESH-VIABILITY] Error checking refresh viability', { correlationId, error: (0, log_safe_1.errForLog)(error) });
|
|
58
|
+
// PAY-1438: the caller gets the id; the log line names the error only (errForLog), never its message (PII-in-logs group 1, BAPert 76553).
|
|
58
59
|
return server_1.NextResponse.json({ canRefresh: false, reason: 'check_error', error: 'Failed to check refresh viability', code: 'INTERNAL_ERROR', correlationId }, { status: 500 });
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -14,6 +14,7 @@ const startup_init_1 = require("../../lib/startup-init");
|
|
|
14
14
|
const idp_client_config_1 = require("../../lib/idp-client-config");
|
|
15
15
|
const token_log_id_1 = require("../../lib/token-log-id");
|
|
16
16
|
const sessions_1 = require("../../vibe/sessions");
|
|
17
|
+
const log_safe_1 = require("../../lib/log-safe");
|
|
17
18
|
async function GET(req) {
|
|
18
19
|
try {
|
|
19
20
|
// Ensure initialization is complete (auth signing secret resolved from IDP)
|
|
@@ -213,7 +214,7 @@ async function GET(req) {
|
|
|
213
214
|
return server_1.NextResponse.json({ authenticated: false });
|
|
214
215
|
}
|
|
215
216
|
catch (error) {
|
|
216
|
-
console.error('[API Viability] Error checking session viability:', error);
|
|
217
|
+
console.error('[API Viability] Error checking session viability:', (0, log_safe_1.errForLog)(error));
|
|
217
218
|
return server_1.NextResponse.json({ authenticated: false }, { status: 500 });
|
|
218
219
|
}
|
|
219
220
|
}
|
package/dist/auth/better-auth.js
CHANGED
|
@@ -76,6 +76,7 @@ const redis_1 = require("../lib/redis");
|
|
|
76
76
|
const single_flight_1 = require("../lib/single-flight");
|
|
77
77
|
const session_store_1 = require("../lib/session-store");
|
|
78
78
|
const token_log_id_1 = require("../lib/token-log-id");
|
|
79
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
79
80
|
/**
|
|
80
81
|
* 185121: short sha256 fingerprint for secrets/tokens in log lines — enough
|
|
81
82
|
* to tell "same" from "divergent", never enough to reconstruct. 12 hex chars
|
|
@@ -740,7 +741,7 @@ function createBetterAuthInstance(idpConfig, opts = {}) {
|
|
|
740
741
|
await deleteSession(key);
|
|
741
742
|
}
|
|
742
743
|
catch (err) {
|
|
743
|
-
console.warn('[BETTER_AUTH] secondary delete: canonical store cleanup failed:', err);
|
|
744
|
+
console.warn('[BETTER_AUTH] secondary delete: canonical store cleanup failed:', (0, log_safe_1.errForLog)(err));
|
|
744
745
|
}
|
|
745
746
|
},
|
|
746
747
|
},
|
|
@@ -1188,7 +1189,7 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
|
|
|
1188
1189
|
return stored;
|
|
1189
1190
|
}
|
|
1190
1191
|
catch (err) {
|
|
1191
|
-
console.error('[BETTER_AUTH] IDP token exchange failed:',
|
|
1192
|
+
console.error('[BETTER_AUTH] IDP token exchange failed:', (0, log_safe_1.errForLog)(err));
|
|
1192
1193
|
return false;
|
|
1193
1194
|
}
|
|
1194
1195
|
}
|
|
@@ -1270,7 +1271,7 @@ async function storeIdpTokensInSession(sessionToken, idpTokenData, authProvider)
|
|
|
1270
1271
|
return true;
|
|
1271
1272
|
}
|
|
1272
1273
|
catch (err) {
|
|
1273
|
-
console.error('[BETTER_AUTH] storeIdpTokensInSession failed:',
|
|
1274
|
+
console.error('[BETTER_AUTH] storeIdpTokensInSession failed:', (0, log_safe_1.errForLog)(err));
|
|
1274
1275
|
return false;
|
|
1275
1276
|
}
|
|
1276
1277
|
}
|
|
@@ -1359,14 +1360,14 @@ async function abortOAuthSignIn(callbackResponse, sessionToken, cookieHeader, lo
|
|
|
1359
1360
|
headers.append('set-cookie', sc);
|
|
1360
1361
|
}
|
|
1361
1362
|
catch (err) {
|
|
1362
|
-
console.warn('[BETTER_AUTH] abort: Better Auth signOut failed, deleting the record directly:',
|
|
1363
|
+
console.warn('[BETTER_AUTH] abort: Better Auth signOut failed, deleting the record directly:', (0, log_safe_1.errForLog)(err));
|
|
1363
1364
|
}
|
|
1364
1365
|
try {
|
|
1365
1366
|
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
1366
1367
|
await (0, redis_1.getRedis)().del(`ba:${config.clientSlug || (0, app_slug_1.getAppSlug)()}:${sessionToken}`);
|
|
1367
1368
|
}
|
|
1368
1369
|
catch (err) {
|
|
1369
|
-
console.error('[BETTER_AUTH] abort: could not delete the Better Auth session record', (0, token_log_id_1.tokenLogId)(sessionToken),
|
|
1370
|
+
console.error('[BETTER_AUTH] abort: could not delete the Better Auth session record', (0, token_log_id_1.tokenLogId)(sessionToken), (0, log_safe_1.errForLog)(err));
|
|
1370
1371
|
}
|
|
1371
1372
|
}
|
|
1372
1373
|
for (const sc of expireSetCookies(callbackResponse.headers.getSetCookie?.() ?? []))
|
|
@@ -1484,7 +1485,7 @@ function createAuthGetHandler(loginPath = '/account-auth/login') {
|
|
|
1484
1485
|
}
|
|
1485
1486
|
}
|
|
1486
1487
|
catch (err) {
|
|
1487
|
-
console.error('[BETTER_AUTH] IDP token exchange failed:',
|
|
1488
|
+
console.error('[BETTER_AUTH] IDP token exchange failed:', (0, log_safe_1.errForLog)(err));
|
|
1488
1489
|
}
|
|
1489
1490
|
if (exchange !== true) {
|
|
1490
1491
|
// A specific IdP error code goes on the URL; otherwise the generic OAUTH_SIGN_IN_FAILED.
|
package/dist/auth/idp-oauth.js
CHANGED
|
@@ -27,6 +27,7 @@ exports.createIdpOAuthHooks = createIdpOAuthHooks;
|
|
|
27
27
|
* case-insensitively, at the authorize step). Both come from ONE value: the `redirectURI` of the provider config.
|
|
28
28
|
*/
|
|
29
29
|
const jose_1 = require("jose");
|
|
30
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
30
31
|
const DISCOVERY_TTL_MS = 10 * 60 * 1000;
|
|
31
32
|
const HTTP_TIMEOUT_MS = 10_000;
|
|
32
33
|
/** The URI sent at authorize AND token time. Explicit env wins; else the registered {app}/api/auth/callback form. */
|
|
@@ -252,7 +253,8 @@ function createIdpOAuthHooks(opts) {
|
|
|
252
253
|
console.warn('[BETTER_AUTH] payez-idp token response address is masked or unusable; ignored');
|
|
253
254
|
const email = resolveEmail(payload, responseEmailUsable ? responseEmailRaw : undefined);
|
|
254
255
|
if (!email) {
|
|
255
|
-
|
|
256
|
+
// A closed `code` rides on the error so the refusal log tells an operator WHY (errForLog prints a closed UPPER_SNAKE code) with no value (BAPert 76998 E11).
|
|
257
|
+
throw Object.assign(new Error(responseMasked ? 'response address is masked' : 'no usable email (needs data.user.email or an email claim, address-shaped, equal when both are present)'), { code: responseMasked ? 'RESPONSE_ADDRESS_MASKED' : 'NO_USABLE_EMAIL' });
|
|
256
258
|
}
|
|
257
259
|
const name = (typeof payload.name === 'string' && payload.name.trim()) ||
|
|
258
260
|
(typeof payload.full_name === 'string' && payload.full_name.trim()) ||
|
|
@@ -272,7 +274,7 @@ function createIdpOAuthHooks(opts) {
|
|
|
272
274
|
}
|
|
273
275
|
catch (err) {
|
|
274
276
|
// Reason only, never the token.
|
|
275
|
-
console.warn('[BETTER_AUTH] payez-idp access token refused:',
|
|
277
|
+
console.warn('[BETTER_AUTH] payez-idp access token refused:', (0, log_safe_1.errForLog)(err));
|
|
276
278
|
return null;
|
|
277
279
|
}
|
|
278
280
|
}
|
|
@@ -19,6 +19,7 @@ exports.idpLogin = idpLogin;
|
|
|
19
19
|
exports.idpRefreshToken = idpRefreshToken;
|
|
20
20
|
exports.idpVerify2FA = idpVerify2FA;
|
|
21
21
|
exports.idpSend2FACode = idpSend2FACode;
|
|
22
|
+
const log_safe_1 = require("../../lib/log-safe");
|
|
22
23
|
// ============================================================================
|
|
23
24
|
// CONFIGURATION
|
|
24
25
|
// ============================================================================
|
|
@@ -95,7 +96,7 @@ async function idpLogin(credentials, clientHeaders) {
|
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
catch (error) {
|
|
98
|
-
console.error('[IDP_CLIENT] Login request failed:', error);
|
|
99
|
+
console.error('[IDP_CLIENT] Login request failed:', (0, log_safe_1.errForLog)(error));
|
|
99
100
|
return {
|
|
100
101
|
success: false,
|
|
101
102
|
error: {
|
|
@@ -211,7 +212,7 @@ async function idpRefreshToken(refreshToken, mfaContext) {
|
|
|
211
212
|
};
|
|
212
213
|
}
|
|
213
214
|
catch (error) {
|
|
214
|
-
console.error('[IDP_CLIENT] Token refresh request failed:', error);
|
|
215
|
+
console.error('[IDP_CLIENT] Token refresh request failed:', (0, log_safe_1.errForLog)(error));
|
|
215
216
|
return {
|
|
216
217
|
success: false,
|
|
217
218
|
error: {
|
|
@@ -261,7 +262,7 @@ async function idpVerify2FA(accessToken, code, method) {
|
|
|
261
262
|
return { success: true };
|
|
262
263
|
}
|
|
263
264
|
catch (error) {
|
|
264
|
-
console.error('[IDP_CLIENT] 2FA verification failed:', error);
|
|
265
|
+
console.error('[IDP_CLIENT] 2FA verification failed:', (0, log_safe_1.errForLog)(error));
|
|
265
266
|
return {
|
|
266
267
|
success: false,
|
|
267
268
|
error: {
|
|
@@ -303,7 +304,7 @@ async function idpSend2FACode(accessToken, method) {
|
|
|
303
304
|
return { success: true };
|
|
304
305
|
}
|
|
305
306
|
catch (error) {
|
|
306
|
-
console.error('[IDP_CLIENT] Send 2FA code failed:', error);
|
|
307
|
+
console.error('[IDP_CLIENT] Send 2FA code failed:', (0, log_safe_1.errForLog)(error));
|
|
307
308
|
return {
|
|
308
309
|
success: false,
|
|
309
310
|
error: {
|
|
@@ -46,7 +46,7 @@ export declare function extractAmrFromToken(decoded: DecodedIdpAccessToken): str
|
|
|
46
46
|
* Check if a token expiry timestamp indicates the token needs refresh.
|
|
47
47
|
*
|
|
48
48
|
* @param expiresAt - Token expiry timestamp (Unix milliseconds)
|
|
49
|
-
* @param bufferMs - How early to trigger refresh (default 5 minutes)
|
|
49
|
+
* @param bufferMs - How early to trigger refresh (default: the one refresh window, `REFRESH_WINDOW_MS`, 5 minutes)
|
|
50
50
|
* @returns true if token is expired or will expire within buffer period
|
|
51
51
|
*/
|
|
52
52
|
export declare function tokenNeedsRefresh(expiresAt: number | undefined, bufferMs?: number): boolean;
|
|
@@ -20,6 +20,8 @@ exports.msUntilExpiry = msUntilExpiry;
|
|
|
20
20
|
exports.expClaimToMs = expClaimToMs;
|
|
21
21
|
exports.validateTokenExpiry = validateTokenExpiry;
|
|
22
22
|
const jwt_decode_1 = require("../../lib/jwt-decode");
|
|
23
|
+
const refresh_window_1 = require("../../lib/refresh-window");
|
|
24
|
+
const log_safe_1 = require("../../lib/log-safe");
|
|
23
25
|
// Re-export header utilities for consumers
|
|
24
26
|
var jwt_decode_2 = require("../../lib/jwt-decode");
|
|
25
27
|
Object.defineProperty(exports, "decodeJwtHeader", { enumerable: true, get: function () { return jwt_decode_2.decodeJwtHeader; } });
|
|
@@ -38,7 +40,7 @@ function decodeIdpAccessToken(token) {
|
|
|
38
40
|
return (0, jwt_decode_1.jwtDecode)(token);
|
|
39
41
|
}
|
|
40
42
|
catch (error) {
|
|
41
|
-
console.error('[TOKEN_UTILS] Failed to decode access token:', error);
|
|
43
|
+
console.error('[TOKEN_UTILS] Failed to decode access token:', (0, log_safe_1.errForLog)(error));
|
|
42
44
|
return null;
|
|
43
45
|
}
|
|
44
46
|
}
|
|
@@ -63,7 +65,7 @@ function decodeIdpAccessTokenFull(token) {
|
|
|
63
65
|
};
|
|
64
66
|
}
|
|
65
67
|
catch (error) {
|
|
66
|
-
console.error('[TOKEN_UTILS] Failed to decode access token (full):', error);
|
|
68
|
+
console.error('[TOKEN_UTILS] Failed to decode access token (full):', (0, log_safe_1.errForLog)(error));
|
|
67
69
|
return null;
|
|
68
70
|
}
|
|
69
71
|
}
|
|
@@ -129,10 +131,10 @@ function extractAmrFromToken(decoded) {
|
|
|
129
131
|
* Check if a token expiry timestamp indicates the token needs refresh.
|
|
130
132
|
*
|
|
131
133
|
* @param expiresAt - Token expiry timestamp (Unix milliseconds)
|
|
132
|
-
* @param bufferMs - How early to trigger refresh (default 5 minutes)
|
|
134
|
+
* @param bufferMs - How early to trigger refresh (default: the one refresh window, `REFRESH_WINDOW_MS`, 5 minutes)
|
|
133
135
|
* @returns true if token is expired or will expire within buffer period
|
|
134
136
|
*/
|
|
135
|
-
function tokenNeedsRefresh(expiresAt, bufferMs =
|
|
137
|
+
function tokenNeedsRefresh(expiresAt, bufferMs = refresh_window_1.REFRESH_WINDOW_MS) {
|
|
136
138
|
if (!expiresAt) {
|
|
137
139
|
return true; // No expiry info = assume needs refresh
|
|
138
140
|
}
|
|
@@ -212,7 +214,7 @@ function validateTokenExpiry(accessToken, cachedExpiresAt) {
|
|
|
212
214
|
};
|
|
213
215
|
}
|
|
214
216
|
catch (error) {
|
|
215
|
-
console.error('[TOKEN_UTILS] Failed to validate token expiry:', error);
|
|
217
|
+
console.error('[TOKEN_UTILS] Failed to validate token expiry:', (0, log_safe_1.errForLog)(error));
|
|
216
218
|
return { valid: false, actualExpiresAt: null, mismatch: false };
|
|
217
219
|
}
|
|
218
220
|
}
|
package/dist/config/logger.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.redisLogger = exports.tokenRefreshLogger = exports.tokenSyncLogger = exports.tokenLogger = exports.circuitBreakerLogger = exports.apiLogger = exports.idpLogger = exports.authLogger = exports.logger = exports.log = void 0;
|
|
4
4
|
const logging_config_1 = require("./logging-config");
|
|
5
5
|
const vibe_log_transport_1 = require("./vibe-log-transport");
|
|
6
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
6
7
|
let loggingConfig = (0, logging_config_1.getLoggingConfigSync)();
|
|
7
8
|
(0, logging_config_1.startConfigWatching)();
|
|
8
9
|
const isEdgeRuntime = () => {
|
|
@@ -48,7 +49,7 @@ const createLogger = () => {
|
|
|
48
49
|
return winston.createLogger({ level: loggingConfig.logLevel, levels: loggingConfig.levels, format: winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json()), transports, exitOnError: false });
|
|
49
50
|
}
|
|
50
51
|
catch (error) {
|
|
51
|
-
console.warn('Failed to initialize Winston logger, falling back to EdgeLogger:', error);
|
|
52
|
+
console.warn('Failed to initialize Winston logger, falling back to EdgeLogger:', (0, log_safe_1.errForLog)(error));
|
|
52
53
|
return new EdgeLogger();
|
|
53
54
|
}
|
|
54
55
|
};
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.removeConfigWatcher = exports.addConfigWatcher = exports.stopConfigWatching = exports.startConfigWatching = exports.getLoggingConfigSync = exports.getLoggingConfig = exports.loggingConfigManager = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const fs_2 = require("fs");
|
|
6
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
6
7
|
const isEdgeRuntime = typeof globalThis.EdgeRuntime !== 'undefined' || process.env.NEXT_RUNTIME === 'edge';
|
|
7
8
|
class LoggingConfigManager {
|
|
8
9
|
config = null;
|
|
@@ -39,7 +40,7 @@ class LoggingConfigManager {
|
|
|
39
40
|
return config;
|
|
40
41
|
}
|
|
41
42
|
catch (error) {
|
|
42
|
-
console.warn(`Failed to load logging config from ${this.configPath}:`, error);
|
|
43
|
+
console.warn(`Failed to load logging config from ${this.configPath}:`, (0, log_safe_1.errForLog)(error));
|
|
43
44
|
return this.getDefaultConfig();
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -61,7 +62,7 @@ class LoggingConfigManager {
|
|
|
61
62
|
} });
|
|
62
63
|
}
|
|
63
64
|
catch (error) {
|
|
64
|
-
console.warn('Failed to start watching logging config file:', error);
|
|
65
|
+
console.warn('Failed to start watching logging config file:', (0, log_safe_1.errForLog)(error));
|
|
65
66
|
} }
|
|
66
67
|
stopWatching() { if (this.isEdgeRuntime || this.isBrowser) {
|
|
67
68
|
return;
|
|
@@ -75,7 +76,7 @@ class LoggingConfigManager {
|
|
|
75
76
|
cb(config);
|
|
76
77
|
}
|
|
77
78
|
catch (error) {
|
|
78
|
-
console.error('Error in logging config watcher:', error);
|
|
79
|
+
console.error('Error in logging config watcher:', (0, log_safe_1.errForLog)(error));
|
|
79
80
|
} }); }
|
|
80
81
|
validateAndMergeConfig(rawConfig) {
|
|
81
82
|
const defaults = this.getDefaultConfig();
|
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.VibeLogTransport = void 0;
|
|
16
16
|
exports.createVibeLogTransport = createVibeLogTransport;
|
|
17
17
|
const redis_1 = require("../lib/redis");
|
|
18
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
18
19
|
// Dynamic import — winston is a peerDependency and may not be installed.
|
|
19
20
|
// We resolve the base class lazily so builds don't break without it.
|
|
20
21
|
let TransportBase;
|
|
@@ -144,7 +145,7 @@ class VibeLogTransport extends TransportBase {
|
|
|
144
145
|
this.batch = [...entries, ...this.batch].slice(0, 100);
|
|
145
146
|
}
|
|
146
147
|
// Log to console as fallback
|
|
147
|
-
console.error('[VibeLogTransport] Failed to flush logs:', err);
|
|
148
|
+
console.error('[VibeLogTransport] Failed to flush logs:', (0, log_safe_1.errForLog)(err));
|
|
148
149
|
}
|
|
149
150
|
finally {
|
|
150
151
|
this.isFlushing = false;
|
package/dist/lib/api-handler.js
CHANGED
|
@@ -21,6 +21,7 @@ const nanoid_1 = require("nanoid");
|
|
|
21
21
|
const session_store_1 = require("./session-store");
|
|
22
22
|
const test_aware_get_token_1 = require("./test-aware-get-token");
|
|
23
23
|
const internal_api_1 = require("./internal-api");
|
|
24
|
+
const refresh_window_1 = require("./refresh-window");
|
|
24
25
|
// ============================================================================
|
|
25
26
|
// API Response Builder (for backward compatibility)
|
|
26
27
|
// ============================================================================
|
|
@@ -189,7 +190,7 @@ class ApiHandler {
|
|
|
189
190
|
// Skip the optimization (hasRefreshToken check) when access token is missing —
|
|
190
191
|
// some session shapes store the refresh token under different field names,
|
|
191
192
|
// and we still want a refresh attempt to populate the access token.
|
|
192
|
-
const thresholdMs =
|
|
193
|
+
const thresholdMs = refresh_window_1.REFRESH_WINDOW_MS;
|
|
193
194
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
195
|
const expires = sessionData.idpAccessTokenExpires || sessionData.accessTokenExpires || 0;
|
|
195
196
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -305,7 +306,7 @@ class ApiHandler {
|
|
|
305
306
|
try {
|
|
306
307
|
// Double-check if still needs refresh
|
|
307
308
|
const latest = await (0, session_store_1.getSession)(sessionToken);
|
|
308
|
-
const thresholdMs =
|
|
309
|
+
const thresholdMs = refresh_window_1.REFRESH_WINDOW_MS;
|
|
309
310
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
310
311
|
const latestAny = latest;
|
|
311
312
|
// Sessions may store the access token under either `accessToken` or `idpAccessToken`,
|
|
@@ -348,7 +349,7 @@ class ApiHandler {
|
|
|
348
349
|
const session = await (0, session_store_1.getSession)(sessionToken);
|
|
349
350
|
if (session?.accessToken) {
|
|
350
351
|
const expires = session.accessTokenExpires || 0;
|
|
351
|
-
if ((expires - Date.now()) >
|
|
352
|
+
if ((expires - Date.now()) > refresh_window_1.REFRESH_WINDOW_MS) {
|
|
352
353
|
return { success: true };
|
|
353
354
|
}
|
|
354
355
|
}
|
|
@@ -4,10 +4,15 @@ export interface EnsureFreshConfig {
|
|
|
4
4
|
refreshEndpoint?: string;
|
|
5
5
|
}
|
|
6
6
|
export interface EnsureFreshOptions {
|
|
7
|
-
/** Refresh if the access token is within this many ms of expiry. Default
|
|
7
|
+
/** Refresh if the access token is within this many ms of expiry. Default REFRESH_WINDOW_MS (5 min, lib/refresh-window). */
|
|
8
8
|
safetyWindowMs?: number;
|
|
9
9
|
/** Max wait while another caller holds the refresh lock. Default 5000. */
|
|
10
10
|
lockWaitMs?: number;
|
|
11
|
+
/**
|
|
12
|
+
* The refresh ROUTE sets this (BAPert 76449, R-over-E PR 2): a session with no refresh token is refused with the terminal NO_REFRESH_TOKEN
|
|
13
|
+
* even when its access token is still fresh, as the route always did. Other callers leave it unset and get `already_fresh` for a fresh token.
|
|
14
|
+
*/
|
|
15
|
+
requireRefreshToken?: boolean;
|
|
11
16
|
/**
|
|
12
17
|
* Optional caller request id, for LOG CORRELATION ONLY. It is never the lock identity: the lock id is minted here with
|
|
13
18
|
* crypto.randomUUID(). (BAPert 76298 (b): the lock id used to be this value, taken from the request's x-request-id header.)
|
|
@@ -20,6 +25,14 @@ export type EnsureFreshResult = {
|
|
|
20
25
|
accessTokenExpires: number;
|
|
21
26
|
/** True if we refreshed (or a concurrent refresh completed); false if the stored token was already fresh. */
|
|
22
27
|
refreshed: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* WHY this result is ok (BAPert 76449, additive; `api-handlers/auth/refresh` reproduces its bodies from it and `lib/token-lifecycle` reads
|
|
30
|
+
* `already_fresh`): 'already_fresh' = nothing was needed; 'completed_by_concurrent_request' = another request refreshed and we returned
|
|
31
|
+
* its result; 'refreshed' = THIS call refreshed.
|
|
32
|
+
*/
|
|
33
|
+
reason: 'refreshed' | 'already_fresh' | 'completed_by_concurrent_request';
|
|
34
|
+
/** Whether the stored session holds a refresh token (the refresh route reports it in its body). */
|
|
35
|
+
hasRefreshToken: boolean;
|
|
23
36
|
/**
|
|
24
37
|
* CARD 320072. Is the token in THIS result actually in the session store?
|
|
25
38
|
*
|
|
@@ -56,4 +69,14 @@ export type EnsureFreshResult = {
|
|
|
56
69
|
retryable?: boolean;
|
|
57
70
|
resolution?: string;
|
|
58
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* The refresh envelope rule (BAPert 76455 (B)): `success` is a boolean; `success:true` needs a `data` object holding a string `access_token`;
|
|
74
|
+
* `success:false` needs `error.code` as a string; `meta`, `request_id` and `time_stamp` are optional; a non-2xx can not say success:true.
|
|
75
|
+
*/
|
|
76
|
+
export declare function checkRefreshEnvelope(body: any, httpOk: boolean): {
|
|
77
|
+
ok: true;
|
|
78
|
+
} | {
|
|
79
|
+
ok: false;
|
|
80
|
+
why: string;
|
|
81
|
+
};
|
|
59
82
|
export declare function ensureFreshAccessToken(sessionToken: string, config: EnsureFreshConfig, options?: EnsureFreshOptions): Promise<EnsureFreshResult>;
|