@payez/next-mvp 4.1.11 → 4.1.20
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/api-handlers/account/profile.d.ts +38 -0
- package/dist/api-handlers/account/profile.js +73 -45
- package/dist/api-handlers/account/recovery/send-code.d.ts +33 -0
- package/dist/api-handlers/account/recovery/send-code.js +52 -18
- package/dist/api-handlers/account/recovery/verify-code.d.ts +33 -0
- package/dist/api-handlers/account/recovery/verify-code.js +52 -18
- package/dist/api-handlers/account/verify-email.js +15 -1
- package/dist/api-handlers/account/verify-sms.js +15 -1
- package/dist/api-handlers/auth/passkey-signin.d.ts +29 -0
- package/dist/api-handlers/auth/passkey-signin.js +97 -0
- package/dist/api-handlers/mfa/backup-code-verify.d.ts +73 -0
- package/dist/api-handlers/mfa/backup-code-verify.js +153 -0
- package/dist/api-handlers/mfa/backup-codes.d.ts +7 -0
- package/dist/api-handlers/mfa/backup-codes.js +40 -0
- package/dist/api-handlers/mfa/methods.d.ts +3 -0
- package/dist/api-handlers/mfa/methods.js +41 -0
- package/dist/api-handlers/mfa/totp-confirm.d.ts +8 -0
- package/dist/api-handlers/mfa/totp-confirm.js +41 -0
- package/dist/api-handlers/mfa/totp-enroll.d.ts +8 -0
- package/dist/api-handlers/mfa/totp-enroll.js +41 -0
- package/dist/api-handlers/mfa/totp-verify.d.ts +47 -0
- package/dist/api-handlers/mfa/totp-verify.js +134 -0
- package/dist/api-handlers/passkeys/list.d.ts +7 -0
- package/dist/api-handlers/passkeys/list.js +29 -0
- package/dist/api-handlers/passkeys/manage.d.ts +3 -0
- package/dist/api-handlers/passkeys/manage.js +54 -0
- package/dist/api-handlers/passkeys/register-options.d.ts +8 -0
- package/dist/api-handlers/passkeys/register-options.js +41 -0
- package/dist/api-handlers/passkeys/register-verify.d.ts +7 -0
- package/dist/api-handlers/passkeys/register-verify.js +40 -0
- package/dist/api-handlers/session/viability.js +19 -0
- package/dist/auth/better-auth.d.ts +68 -5
- package/dist/auth/better-auth.js +256 -42
- package/dist/auth/utils/idp-client.d.ts +13 -22
- package/dist/auth/utils/idp-client.js +12 -82
- package/dist/client/better-auth-client.d.ts +14 -2
- package/dist/lib/session-store.js +28 -21
- package/dist/lib/startup-init.js +19 -19
- package/dist/lib/token-lifecycle.js +40 -4
- package/dist/models/SessionModel.d.ts +17 -0
- package/dist/pages/login/page.d.ts +10 -1
- package/dist/pages/login/page.js +69 -80
- package/dist/pages/verify-code/page.d.ts +10 -1
- package/dist/pages/verify-code/page.js +72 -4
- package/dist/routes/auth/passkey-signin.d.ts +13 -0
- package/dist/routes/auth/passkey-signin.js +18 -0
- package/dist/routes/auth/viability.d.ts +2 -0
- package/dist/routes/auth/viability.js +88 -0
- package/dist/routes/mfa/backup-code-verify.d.ts +1 -0
- package/dist/routes/mfa/backup-code-verify.js +5 -0
- package/dist/routes/mfa/backup-codes.d.ts +10 -0
- package/dist/routes/mfa/backup-codes.js +14 -0
- package/dist/routes/mfa/methods.d.ts +21 -0
- package/dist/routes/mfa/methods.js +10 -0
- package/dist/routes/mfa/totp-confirm.d.ts +10 -0
- package/dist/routes/mfa/totp-confirm.js +14 -0
- package/dist/routes/mfa/totp-enroll.d.ts +10 -0
- package/dist/routes/mfa/totp-enroll.js +14 -0
- package/dist/routes/mfa/totp-verify.d.ts +14 -0
- package/dist/routes/mfa/totp-verify.js +18 -0
- package/dist/routes/passkeys/list.d.ts +10 -0
- package/dist/routes/passkeys/list.js +14 -0
- package/dist/routes/passkeys/manage.d.ts +18 -0
- package/dist/routes/passkeys/manage.js +13 -0
- package/dist/routes/passkeys/register-options.d.ts +10 -0
- package/dist/routes/passkeys/register-options.js +14 -0
- package/dist/routes/passkeys/register-verify.d.ts +10 -0
- package/dist/routes/passkeys/register-verify.js +14 -0
- package/dist/server/auth.d.ts +2 -2
- package/dist/server/decode-session.js +37 -3
- package/package.json +958 -893
- package/scripts/check-internal-url-usage.sh +73 -73
- package/src/api/index.ts +17 -17
- package/src/api-handlers/account/masked-info.ts +45 -45
- package/src/api-handlers/account/profile.ts +99 -80
- package/src/api-handlers/account/recovery/initiate.ts +23 -23
- package/src/api-handlers/account/recovery/send-code.ts +73 -25
- package/src/api-handlers/account/recovery/verify-code.ts +73 -25
- package/src/api-handlers/account/reset-password.ts +23 -23
- package/src/api-handlers/account/send-code.ts +76 -76
- package/src/api-handlers/account/validate-password.ts +118 -118
- package/src/api-handlers/account/verify-email.ts +15 -1
- package/src/api-handlers/account/verify-sms.ts +15 -1
- package/src/api-handlers/admin/stats.ts +249 -249
- package/src/api-handlers/anon/preferences.ts +123 -123
- package/src/api-handlers/auth/jwks.ts +20 -20
- package/src/api-handlers/auth/login.ts +240 -240
- package/src/api-handlers/auth/passkey-signin.ts +98 -0
- package/src/api-handlers/auth/validate.ts +43 -43
- package/src/api-handlers/mfa/backup-code-verify.ts +175 -0
- package/src/api-handlers/mfa/backup-codes.ts +41 -0
- package/src/api-handlers/mfa/methods.ts +46 -0
- package/src/api-handlers/mfa/totp-confirm.ts +42 -0
- package/src/api-handlers/mfa/totp-enroll.ts +42 -0
- package/src/api-handlers/mfa/totp-verify.ts +156 -0
- package/src/api-handlers/passkeys/list.ts +31 -0
- package/src/api-handlers/passkeys/manage.ts +54 -0
- package/src/api-handlers/passkeys/register-options.ts +42 -0
- package/src/api-handlers/passkeys/register-verify.ts +41 -0
- package/src/api-handlers/session/viability.ts +161 -142
- package/src/auth/better-auth.ts +247 -6
- package/src/auth/unauthenticated-routes.ts +2 -2
- package/src/auth/utils/idp-client.ts +13 -95
- package/src/client/better-auth-client.ts +70 -70
- package/src/client/fetchWithSession.ts +21 -21
- package/src/client/index.ts +13 -13
- package/src/client/useAnonSession.ts +131 -131
- package/src/components/admin/VibeAdminContext.tsx +87 -87
- package/src/config/logging-config.ts +82 -82
- package/src/config/unauthenticated-routes.ts +19 -19
- package/src/edge/internal-api-url.ts +65 -65
- package/src/edge/middleware.ts +42 -42
- package/src/hooks/useProfile.ts +75 -75
- package/src/lib/anon-session.ts +213 -213
- package/src/lib/auth-secret.ts +137 -137
- package/src/lib/demo-mode.ts +17 -17
- package/src/lib/idp-client-config.ts +559 -559
- package/src/lib/idp-fetch.ts +126 -126
- package/src/lib/internal-api.ts +171 -171
- package/src/lib/jwt-decode-client.ts +45 -45
- package/src/lib/rate-limit-service.ts +9 -9
- package/src/lib/redis.ts +26 -26
- package/src/lib/refresh-token-validator.ts +64 -64
- package/src/lib/secret-validation.ts +8 -8
- package/src/lib/session-store.ts +702 -695
- package/src/lib/session.ts +34 -34
- package/src/lib/startup-init.ts +251 -251
- package/src/lib/token-expiry.ts +40 -40
- package/src/lib/token-lifecycle.ts +533 -497
- package/src/lib/types/api-responses.ts +336 -336
- package/src/middleware/auth-decision.ts +43 -43
- package/src/models/DecodedAccessToken.ts +16 -16
- package/src/models/SessionModel.ts +282 -264
- package/src/pages/admin-roles/index.ts +9 -9
- package/src/pages/login/page.tsx +123 -228
- package/src/pages/security/EnhancedSecurityPage.tsx +9 -9
- package/src/pages/verify-code/page.tsx +71 -5
- package/src/routes/account/index.ts +31 -31
- package/src/routes/account/masked-info.ts +36 -36
- package/src/routes/account/send-code.ts +39 -39
- package/src/routes/account/verify-email.ts +40 -40
- package/src/routes/account/verify-sms.ts +40 -40
- package/src/routes/auth/index.ts +22 -22
- package/src/routes/auth/passkey-signin.ts +13 -0
- package/src/routes/auth/refresh.ts +48 -48
- package/src/routes/auth/session.ts +166 -166
- package/src/routes/auth/viability.ts +89 -0
- package/src/routes/index.ts +17 -17
- package/src/routes/mfa/backup-code-verify.ts +1 -0
- package/src/routes/mfa/backup-codes.ts +10 -0
- package/src/routes/mfa/methods.ts +22 -0
- package/src/routes/mfa/totp-confirm.ts +10 -0
- package/src/routes/mfa/totp-enroll.ts +10 -0
- package/src/routes/mfa/totp-verify.ts +14 -0
- package/src/routes/passkeys/list.ts +10 -0
- package/src/routes/passkeys/manage.ts +23 -0
- package/src/routes/passkeys/register-options.ts +10 -0
- package/src/routes/passkeys/register-verify.ts +10 -0
- package/src/routes/session/index.ts +7 -7
- package/src/routes/session/refresh-viability.ts +17 -17
- package/src/server/auth.ts +373 -373
- package/src/server/decode-session.ts +235 -202
- package/src/services/signalrActivityService.ts +258 -258
- package/src/theme/ThemeProvider.tsx +39 -39
- package/src/theme/default.ts +33 -33
- package/src/theme/index.ts +31 -31
- package/src/theme/types.ts +69 -69
- package/src/theme/useTheme.ts +57 -57
- package/src/theme/utils.ts +40 -40
- package/src/types/api.ts +12 -12
- package/src/types/auth.d.ts +23 -23
- package/src/types/auth.ts +22 -22
- package/src/types/logging.ts +11 -11
- package/src/types/recovery.ts +54 -54
- package/src/utils/api.ts +353 -353
- package/src/utils/circuitBreaker.ts +40 -40
- package/src/utils/error-message.ts +108 -108
- package/src/utils/layout/reservedSpace.ts +124 -124
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
# CI Check: Detect incorrect internal API URL patterns
|
|
4
|
-
#
|
|
5
|
-
# This script fails if code is using req.url or req.nextUrl.origin to construct
|
|
6
|
-
# URLs for internal API calls. These patterns cause 403 errors in Kubernetes
|
|
7
|
-
# because they use the external HTTPS URL instead of INTERNAL_API_URL.
|
|
8
|
-
#
|
|
9
|
-
# CORRECT: Use internalFetch() or internalRefresh() from @payez/next-mvp/lib/internal-api
|
|
10
|
-
# INCORRECT: new URL('/api/...', req.url) or fetch(`${req.nextUrl.origin}/api/...`)
|
|
11
|
-
#
|
|
12
|
-
# NOTE: new URL(req.url) alone is FINE - it just parses the incoming request URL.
|
|
13
|
-
# The problem is when you construct a NEW path using req.url as the base.
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
set -e
|
|
17
|
-
|
|
18
|
-
SEARCH_PATH="${1:-.}"
|
|
19
|
-
ERRORS_FOUND=0
|
|
20
|
-
|
|
21
|
-
echo "Checking for incorrect internal URL patterns in: $SEARCH_PATH"
|
|
22
|
-
echo "========================================================================"
|
|
23
|
-
|
|
24
|
-
# Pattern 1: new URL('/api/...', req.url) - constructing internal API URL from request
|
|
25
|
-
# This catches: new URL('/api/...', req.url) and new URL("/api/...", req.url)
|
|
26
|
-
# But NOT: new URL(req.url) which is just parsing the request
|
|
27
|
-
echo ""
|
|
28
|
-
echo "Checking for: new URL('/api/...', req.url) pattern"
|
|
29
|
-
if grep -rn --include="*.ts" --include="*.tsx" -E "new URL\(['\"][^'\"]*['\"],\s*req\.url" "$SEARCH_PATH" 2>/dev/null | grep -v "node_modules" | grep -v "\.d\.ts"; then
|
|
30
|
-
echo "ERROR: Found 'new URL(path, req.url)' pattern. Use internalFetch() instead."
|
|
31
|
-
ERRORS_FOUND=1
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
# Pattern 2: new URL('/api/...', req.nextUrl) or new URL(..., req.nextUrl.origin)
|
|
35
|
-
echo ""
|
|
36
|
-
echo "Checking for: new URL(..., req.nextUrl) pattern"
|
|
37
|
-
if grep -rn --include="*.ts" --include="*.tsx" -E "new URL\([^)]+,\s*req\.nextUrl" "$SEARCH_PATH" 2>/dev/null | grep -v "node_modules" | grep -v "\.d\.ts"; then
|
|
38
|
-
echo "ERROR: Found 'new URL(..., req.nextUrl)' pattern. Use internalFetch() instead."
|
|
39
|
-
ERRORS_FOUND=1
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# Pattern 3: fetch with req.nextUrl.origin and /api/
|
|
43
|
-
echo ""
|
|
44
|
-
echo "Checking for: fetch with req.nextUrl.origin and /api/"
|
|
45
|
-
if grep -rn --include="*.ts" --include="*.tsx" "req\.nextUrl\.origin.*\/api\/" "$SEARCH_PATH" 2>/dev/null | grep -v "node_modules" | grep -v "\.d\.ts"; then
|
|
46
|
-
echo "ERROR: Found 'req.nextUrl.origin' used with internal API route. Use internalFetch() instead."
|
|
47
|
-
ERRORS_FOUND=1
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Pattern 4: Direct fetch to /api/auth/refresh without using internalRefresh (warning only)
|
|
51
|
-
echo ""
|
|
52
|
-
echo "Checking for: Direct fetch to /api/auth/refresh (should use internalRefresh)"
|
|
53
|
-
if grep -rn --include="*.ts" --include="*.tsx" "fetch.*\/api\/auth\/refresh" "$SEARCH_PATH" 2>/dev/null | grep -v "node_modules" | grep -v "\.d\.ts" | grep -v "internal-api\.ts"; then
|
|
54
|
-
echo "WARNING: Found direct fetch to /api/auth/refresh. Consider using internalRefresh() helper."
|
|
55
|
-
# Not failing on this one as it might be intentional in some cases (client-side refresh)
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
echo ""
|
|
59
|
-
echo "========================================================================"
|
|
60
|
-
|
|
61
|
-
if [ $ERRORS_FOUND -eq 1 ]; then
|
|
62
|
-
echo "FAILED: Dangerous internal URL patterns detected!"
|
|
63
|
-
echo ""
|
|
64
|
-
echo "Fix: Import and use helpers from @payez/next-mvp/lib/internal-api:"
|
|
65
|
-
echo " import { internalFetch, internalRefresh } from '@payez/next-mvp/lib/internal-api';"
|
|
66
|
-
echo ""
|
|
67
|
-
echo "Why: Server-to-server calls must use INTERNAL_API_URL (http://service.namespace.svc.cluster.local)"
|
|
68
|
-
echo " not the external URL from the request (https://api.example.com) which fails in Kubernetes."
|
|
69
|
-
exit 1
|
|
70
|
-
else
|
|
71
|
-
echo "PASSED: No dangerous internal URL patterns found."
|
|
72
|
-
exit 0
|
|
73
|
-
fi
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# CI Check: Detect incorrect internal API URL patterns
|
|
4
|
+
#
|
|
5
|
+
# This script fails if code is using req.url or req.nextUrl.origin to construct
|
|
6
|
+
# URLs for internal API calls. These patterns cause 403 errors in Kubernetes
|
|
7
|
+
# because they use the external HTTPS URL instead of INTERNAL_API_URL.
|
|
8
|
+
#
|
|
9
|
+
# CORRECT: Use internalFetch() or internalRefresh() from @payez/next-mvp/lib/internal-api
|
|
10
|
+
# INCORRECT: new URL('/api/...', req.url) or fetch(`${req.nextUrl.origin}/api/...`)
|
|
11
|
+
#
|
|
12
|
+
# NOTE: new URL(req.url) alone is FINE - it just parses the incoming request URL.
|
|
13
|
+
# The problem is when you construct a NEW path using req.url as the base.
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
set -e
|
|
17
|
+
|
|
18
|
+
SEARCH_PATH="${1:-.}"
|
|
19
|
+
ERRORS_FOUND=0
|
|
20
|
+
|
|
21
|
+
echo "Checking for incorrect internal URL patterns in: $SEARCH_PATH"
|
|
22
|
+
echo "========================================================================"
|
|
23
|
+
|
|
24
|
+
# Pattern 1: new URL('/api/...', req.url) - constructing internal API URL from request
|
|
25
|
+
# This catches: new URL('/api/...', req.url) and new URL("/api/...", req.url)
|
|
26
|
+
# But NOT: new URL(req.url) which is just parsing the request
|
|
27
|
+
echo ""
|
|
28
|
+
echo "Checking for: new URL('/api/...', req.url) pattern"
|
|
29
|
+
if grep -rn --include="*.ts" --include="*.tsx" -E "new URL\(['\"][^'\"]*['\"],\s*req\.url" "$SEARCH_PATH" 2>/dev/null | grep -v "node_modules" | grep -v "\.d\.ts"; then
|
|
30
|
+
echo "ERROR: Found 'new URL(path, req.url)' pattern. Use internalFetch() instead."
|
|
31
|
+
ERRORS_FOUND=1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Pattern 2: new URL('/api/...', req.nextUrl) or new URL(..., req.nextUrl.origin)
|
|
35
|
+
echo ""
|
|
36
|
+
echo "Checking for: new URL(..., req.nextUrl) pattern"
|
|
37
|
+
if grep -rn --include="*.ts" --include="*.tsx" -E "new URL\([^)]+,\s*req\.nextUrl" "$SEARCH_PATH" 2>/dev/null | grep -v "node_modules" | grep -v "\.d\.ts"; then
|
|
38
|
+
echo "ERROR: Found 'new URL(..., req.nextUrl)' pattern. Use internalFetch() instead."
|
|
39
|
+
ERRORS_FOUND=1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Pattern 3: fetch with req.nextUrl.origin and /api/
|
|
43
|
+
echo ""
|
|
44
|
+
echo "Checking for: fetch with req.nextUrl.origin and /api/"
|
|
45
|
+
if grep -rn --include="*.ts" --include="*.tsx" "req\.nextUrl\.origin.*\/api\/" "$SEARCH_PATH" 2>/dev/null | grep -v "node_modules" | grep -v "\.d\.ts"; then
|
|
46
|
+
echo "ERROR: Found 'req.nextUrl.origin' used with internal API route. Use internalFetch() instead."
|
|
47
|
+
ERRORS_FOUND=1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Pattern 4: Direct fetch to /api/auth/refresh without using internalRefresh (warning only)
|
|
51
|
+
echo ""
|
|
52
|
+
echo "Checking for: Direct fetch to /api/auth/refresh (should use internalRefresh)"
|
|
53
|
+
if grep -rn --include="*.ts" --include="*.tsx" "fetch.*\/api\/auth\/refresh" "$SEARCH_PATH" 2>/dev/null | grep -v "node_modules" | grep -v "\.d\.ts" | grep -v "internal-api\.ts"; then
|
|
54
|
+
echo "WARNING: Found direct fetch to /api/auth/refresh. Consider using internalRefresh() helper."
|
|
55
|
+
# Not failing on this one as it might be intentional in some cases (client-side refresh)
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo ""
|
|
59
|
+
echo "========================================================================"
|
|
60
|
+
|
|
61
|
+
if [ $ERRORS_FOUND -eq 1 ]; then
|
|
62
|
+
echo "FAILED: Dangerous internal URL patterns detected!"
|
|
63
|
+
echo ""
|
|
64
|
+
echo "Fix: Import and use helpers from @payez/next-mvp/lib/internal-api:"
|
|
65
|
+
echo " import { internalFetch, internalRefresh } from '@payez/next-mvp/lib/internal-api';"
|
|
66
|
+
echo ""
|
|
67
|
+
echo "Why: Server-to-server calls must use INTERNAL_API_URL (http://service.namespace.svc.cluster.local)"
|
|
68
|
+
echo " not the external URL from the request (https://api.example.com) which fails in Kubernetes."
|
|
69
|
+
exit 1
|
|
70
|
+
else
|
|
71
|
+
echo "PASSED: No dangerous internal URL patterns found."
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
package/src/api/index.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @payez/next-mvp API Module Exports
|
|
3
|
-
*
|
|
4
|
-
* Provides enhanced API route handlers with automatic token management
|
|
5
|
-
*
|
|
6
|
-
* @version 2.0.0
|
|
7
|
-
* @since auth-ready-v2
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
createAuthHandler,
|
|
12
|
-
type AuthContext,
|
|
13
|
-
type AuthHandlerOptions,
|
|
14
|
-
type HandlerFunction
|
|
15
|
-
} from './auth-handler';
|
|
16
|
-
|
|
17
|
-
// Default export for convenience
|
|
1
|
+
/**
|
|
2
|
+
* @payez/next-mvp API Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Provides enhanced API route handlers with automatic token management
|
|
5
|
+
*
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
* @since auth-ready-v2
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
createAuthHandler,
|
|
12
|
+
type AuthContext,
|
|
13
|
+
type AuthHandlerOptions,
|
|
14
|
+
type HandlerFunction
|
|
15
|
+
} from './auth-handler';
|
|
16
|
+
|
|
17
|
+
// Default export for convenience
|
|
18
18
|
export { default } from './auth-handler';
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { idpFetchJSON } from '../../lib/idp-fetch';
|
|
3
|
-
import { ENV_CONFIG } from '../../config/env';
|
|
4
|
-
|
|
5
|
-
// IDP masked-info is POST and uses capital 'A' in /api/Account
|
|
6
|
-
export async function POST(req: NextRequest) {
|
|
7
|
-
const url = `${ENV_CONFIG.IDP_URL}/api/Account/masked-info`;
|
|
8
|
-
|
|
9
|
-
// Forward request body if present; IDP often accepts empty object
|
|
10
|
-
let body = '{}';
|
|
11
|
-
try {
|
|
12
|
-
const raw = await req.text();
|
|
13
|
-
if (raw && raw.trim().length > 0) body = raw;
|
|
14
|
-
} catch {}
|
|
15
|
-
|
|
16
|
-
const result = await idpFetchJSON(req, url, {
|
|
17
|
-
method: 'POST',
|
|
18
|
-
headers: { 'Content-Type': 'application/json' },
|
|
19
|
-
body
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
if (!result.ok) {
|
|
23
|
-
return NextResponse.json(
|
|
24
|
-
{
|
|
25
|
-
success: false,
|
|
26
|
-
message: 'Upstream error',
|
|
27
|
-
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: result.json },
|
|
28
|
-
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
29
|
-
},
|
|
30
|
-
{ status: result.status }
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const bodyJson: any = result.json;
|
|
35
|
-
// Unwrap if IDP returns envelope { success, data }
|
|
36
|
-
if (bodyJson && typeof bodyJson === 'object' && 'success' in bodyJson && 'data' in bodyJson) {
|
|
37
|
-
if ((bodyJson as any).success === true) {
|
|
38
|
-
return NextResponse.json((bodyJson as any).data, { status: 200 });
|
|
39
|
-
}
|
|
40
|
-
return NextResponse.json(bodyJson, { status: 200 });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Passthrough otherwise
|
|
44
|
-
return NextResponse.json(bodyJson ?? {}, { status: 200 });
|
|
45
|
-
}
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { idpFetchJSON } from '../../lib/idp-fetch';
|
|
3
|
+
import { ENV_CONFIG } from '../../config/env';
|
|
4
|
+
|
|
5
|
+
// IDP masked-info is POST and uses capital 'A' in /api/Account
|
|
6
|
+
export async function POST(req: NextRequest) {
|
|
7
|
+
const url = `${ENV_CONFIG.IDP_URL}/api/Account/masked-info`;
|
|
8
|
+
|
|
9
|
+
// Forward request body if present; IDP often accepts empty object
|
|
10
|
+
let body = '{}';
|
|
11
|
+
try {
|
|
12
|
+
const raw = await req.text();
|
|
13
|
+
if (raw && raw.trim().length > 0) body = raw;
|
|
14
|
+
} catch {}
|
|
15
|
+
|
|
16
|
+
const result = await idpFetchJSON(req, url, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'Content-Type': 'application/json' },
|
|
19
|
+
body
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!result.ok) {
|
|
23
|
+
return NextResponse.json(
|
|
24
|
+
{
|
|
25
|
+
success: false,
|
|
26
|
+
message: 'Upstream error',
|
|
27
|
+
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: result.json },
|
|
28
|
+
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
29
|
+
},
|
|
30
|
+
{ status: result.status }
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const bodyJson: any = result.json;
|
|
35
|
+
// Unwrap if IDP returns envelope { success, data }
|
|
36
|
+
if (bodyJson && typeof bodyJson === 'object' && 'success' in bodyJson && 'data' in bodyJson) {
|
|
37
|
+
if ((bodyJson as any).success === true) {
|
|
38
|
+
return NextResponse.json((bodyJson as any).data, { status: 200 });
|
|
39
|
+
}
|
|
40
|
+
return NextResponse.json(bodyJson, { status: 200 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Passthrough otherwise
|
|
44
|
+
return NextResponse.json(bodyJson ?? {}, { status: 200 });
|
|
45
|
+
}
|
|
@@ -1,80 +1,99 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { idpFetchJSON } from '../../lib/idp-fetch';
|
|
3
|
+
import { ENV_CONFIG } from '../../config/env';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Account Profile API Handler
|
|
7
|
+
*
|
|
8
|
+
* GET /api/account/profile - Get user profile
|
|
9
|
+
* PUT /api/account/profile - Update user profile
|
|
10
|
+
*
|
|
11
|
+
* WHY THIS WAS REWRITTEN (2026-08-02, QAPert defect #7)
|
|
12
|
+
* Reported as "/api/account/profile 401s on the same cookie masked-info
|
|
13
|
+
* accepts (200)". Two endpoints disagreeing about one cookie meant two auth
|
|
14
|
+
* implementations, and they were:
|
|
15
|
+
*
|
|
16
|
+
* masked-info.ts idpFetchJSON(req, ...) resolves the caller's token
|
|
17
|
+
* from the server-side session
|
|
18
|
+
* profile.ts req.headers.get('authorization') required an INBOUND bearer
|
|
19
|
+
*
|
|
20
|
+
* A browser sends a session COOKIE and no Authorization header, so the old
|
|
21
|
+
* profile.ts 401'd EVERY browser request by construction. Nothing was revoked
|
|
22
|
+
* and no token was stale — the handler demanded a credential the app does not
|
|
23
|
+
* use.
|
|
24
|
+
*
|
|
25
|
+
* THE OLD AUTH CHECK WAS A VENEER. It tested that the header was PRESENT, never
|
|
26
|
+
* that it was valid, then forwarded it upstream unexamined. Any string at all
|
|
27
|
+
* got past it and the IDP did the real work. It looked like enforcement and
|
|
28
|
+
* enforced nothing.
|
|
29
|
+
*
|
|
30
|
+
* WHAT THE WORKAROUND COST US: consumers could not call this with a cookie, so
|
|
31
|
+
* the graft's useProfile hook fetched the raw IDP access token from
|
|
32
|
+
* /api/session/idp-token INTO BROWSER JAVASCRIPT and re-sent it as a bearer.
|
|
33
|
+
* That defeats the reason idpFetchJSON exists — it keeps the token server-side
|
|
34
|
+
* precisely so client-side script can never hold it. The bearer-only handler
|
|
35
|
+
* did not merely fail; it pushed its callers into exposing the token.
|
|
36
|
+
*
|
|
37
|
+
* Switching to idpFetchJSON makes the cookie work, so the hook's token round
|
|
38
|
+
* trip can be deleted and /api/session/idp-token drops to a single deliberate
|
|
39
|
+
* caller: the jwt-inspect debug page.
|
|
40
|
+
*
|
|
41
|
+
* @package @payez/next-mvp
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export async function GET(req: NextRequest) {
|
|
45
|
+
const result = await idpFetchJSON(req, `${ENV_CONFIG.IDP_URL}/api/Account/profile`, {
|
|
46
|
+
method: 'GET',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!result.ok) {
|
|
51
|
+
return NextResponse.json(
|
|
52
|
+
{
|
|
53
|
+
success: false,
|
|
54
|
+
error: {
|
|
55
|
+
code: 'UPSTREAM_SERVICE_ERROR',
|
|
56
|
+
message: result.json?.message || 'Failed to fetch profile',
|
|
57
|
+
status: result.status,
|
|
58
|
+
},
|
|
59
|
+
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
60
|
+
},
|
|
61
|
+
{ status: result.status }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return NextResponse.json(result.json, { status: 200 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function PUT(req: NextRequest) {
|
|
69
|
+
let body = '{}';
|
|
70
|
+
try {
|
|
71
|
+
const raw = await req.text();
|
|
72
|
+
if (raw && raw.trim().length > 0) body = raw;
|
|
73
|
+
} catch {
|
|
74
|
+
// Empty body is valid here; the IDP validates the payload.
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result = await idpFetchJSON(req, `${ENV_CONFIG.IDP_URL}/api/Account/profile`, {
|
|
78
|
+
method: 'PUT',
|
|
79
|
+
headers: { 'Content-Type': 'application/json' },
|
|
80
|
+
body,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!result.ok) {
|
|
84
|
+
return NextResponse.json(
|
|
85
|
+
{
|
|
86
|
+
success: false,
|
|
87
|
+
error: {
|
|
88
|
+
code: 'UPSTREAM_SERVICE_ERROR',
|
|
89
|
+
message: result.json?.message || 'Failed to update profile',
|
|
90
|
+
status: result.status,
|
|
91
|
+
},
|
|
92
|
+
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
93
|
+
},
|
|
94
|
+
{ status: result.status }
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return NextResponse.json(result.json, { status: 200 });
|
|
99
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
|
|
3
|
-
function getIdpUrl(): string {
|
|
4
|
-
const url = process.env.IDP_URL;
|
|
5
|
-
if (!url) throw new Error('[IDP_URL] FATAL: IDP_URL environment variable is REQUIRED.');
|
|
6
|
-
return url;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function POST(req: NextRequest) {
|
|
10
|
-
const IDP_URL = getIdpUrl();
|
|
11
|
-
try {
|
|
12
|
-
const { email } = await req.json();
|
|
13
|
-
const upstream = await fetch(`${IDP_URL}/api/Account/recovery/initiate`, {
|
|
14
|
-
method: 'POST',
|
|
15
|
-
headers: { 'Content-Type': 'application/json' },
|
|
16
|
-
body: JSON.stringify({ email }),
|
|
17
|
-
});
|
|
18
|
-
const data = await upstream.json().catch(() => ({}));
|
|
19
|
-
return NextResponse.json(data, { status: upstream.status });
|
|
20
|
-
} catch (e) {
|
|
21
|
-
return NextResponse.json({ success: false, error: 'initiate_failed' }, { status: 500 });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
function getIdpUrl(): string {
|
|
4
|
+
const url = process.env.IDP_URL;
|
|
5
|
+
if (!url) throw new Error('[IDP_URL] FATAL: IDP_URL environment variable is REQUIRED.');
|
|
6
|
+
return url;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function POST(req: NextRequest) {
|
|
10
|
+
const IDP_URL = getIdpUrl();
|
|
11
|
+
try {
|
|
12
|
+
const { email } = await req.json();
|
|
13
|
+
const upstream = await fetch(`${IDP_URL}/api/Account/recovery/initiate`, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16
|
+
body: JSON.stringify({ email }),
|
|
17
|
+
});
|
|
18
|
+
const data = await upstream.json().catch(() => ({}));
|
|
19
|
+
return NextResponse.json(data, { status: upstream.status });
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return NextResponse.json({ success: false, error: 'initiate_failed' }, { status: 500 });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,25 +1,73 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { idpFetchJSON } from '../../../lib/idp-fetch';
|
|
3
|
+
import { ENV_CONFIG } from '../../../config/env';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Account recovery — send-code
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS WAS REWRITTEN (2026-08-02, swept out of QAPert defect #7)
|
|
9
|
+
* This handler read the caller's bearer straight off the inbound request:
|
|
10
|
+
*
|
|
11
|
+
* const auth = req.headers.get('authorization') || '';
|
|
12
|
+
* const token = auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
|
13
|
+
* ... headers: { 'Authorization': `Bearer ${token}` }
|
|
14
|
+
*
|
|
15
|
+
* Browsers send a session COOKIE and no Authorization header, so `token` was
|
|
16
|
+
* ALWAYS the empty string and this always called the IDP with a literal
|
|
17
|
+
* "Bearer " and nothing after it.
|
|
18
|
+
*
|
|
19
|
+
* THAT IS WORSE THAN THE profile.ts VERSION OF THIS BUG, in two ways:
|
|
20
|
+
*
|
|
21
|
+
* 1. profile.ts at least 401'd when the header was missing. This one has no
|
|
22
|
+
* check at all — `|| ''` swallows the absence — so it proceeds to call
|
|
23
|
+
* upstream with an empty credential and returns the IDP's rejection as an
|
|
24
|
+
* opaque failure. The user sees recovery "not working", not "not
|
|
25
|
+
* authenticated", and the log shows an upstream error rather than an auth
|
|
26
|
+
* one. The defect hides itself.
|
|
27
|
+
* 2. It is BOTH HALVES of account recovery — send-code and verify-code — so
|
|
28
|
+
* the entire recovery path is unreachable, not merely degraded. This is
|
|
29
|
+
* the flow a locked-out user reaches for, i.e. it fails exactly when it is
|
|
30
|
+
* the only thing left that could help them.
|
|
31
|
+
*
|
|
32
|
+
* Fixed the same way as profile.ts and masked-info.ts: idpFetchJSON resolves
|
|
33
|
+
* the caller's token from the server-side session, so the cookie works and the
|
|
34
|
+
* token is never handed to browser JavaScript.
|
|
35
|
+
*
|
|
36
|
+
* @package @payez/next-mvp
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
export async function POST(req: NextRequest) {
|
|
40
|
+
let payload: unknown = {};
|
|
41
|
+
try {
|
|
42
|
+
payload = await req.json();
|
|
43
|
+
} catch {
|
|
44
|
+
return NextResponse.json(
|
|
45
|
+
{ success: false, error: 'invalid_request', message: 'A JSON body is required' },
|
|
46
|
+
{ status: 400 }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = await idpFetchJSON(
|
|
51
|
+
req,
|
|
52
|
+
`${ENV_CONFIG.IDP_URL}/api/Account/recovery/send-code`,
|
|
53
|
+
{
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: { 'Content-Type': 'application/json' },
|
|
56
|
+
body: JSON.stringify(payload),
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!result.ok) {
|
|
61
|
+
return NextResponse.json(
|
|
62
|
+
{
|
|
63
|
+
success: false,
|
|
64
|
+
error: 'send_code_failed',
|
|
65
|
+
message: result.json?.message || 'Upstream error',
|
|
66
|
+
meta: { status: result.status, attemptedRefresh: result.attemptedRefresh },
|
|
67
|
+
},
|
|
68
|
+
{ status: result.status }
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return NextResponse.json(result.json, { status: 200 });
|
|
73
|
+
}
|