@payez/next-mvp 4.8.7 → 4.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -548,7 +548,7 @@ import { POST as logout } from '@payez/next-mvp/routes/auth/logout';
548
548
  | Variable | Required | Description |
549
549
  |----------|----------|-------------|
550
550
  | `CLIENT_ID` | ✅ Yes | Your PayEz IDP client ID (string slug, e.g., `payez_idp_admin_web`) |
551
- | `IDP_URL` | ✅ Yes | PayEz IDP base URL (e.g., `https://idp.payez.net` or `http://localhost:32785`) |
551
+ | `IDP_URL` | ✅ Yes | PayEz IDP base URL (e.g., `https://idp.payez.net` or `http://localhost:32785`). **No default in code.** Call `requireIdpUrl()` from `@payez/next-mvp/lib/require-idp-url` in your `instrumentation.ts` (4.8.8) and the server refuses to start, with one fixed sentence, when it is missing or not a full http(s) address. The quickstart generator writes the production address into `.env.local` and adds that file for you. |
552
552
  | `AUTH_TRUST_HOST` | ✅ Yes | Must be `true` - NextAuth derives OAuth URLs from request headers |
553
553
  | `NEXTAUTH_SECRET` | 🔄 Broker | **Do NOT set** - fetched automatically from IDP at startup (see below) |
554
554
  | `CLIENT_SECRET` | ⚠️ Legacy | Not needed with broker mode - IDP handles signing |
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 4.8.8 (item 24, Jon: "fast and loud are how i like my config errors"): the app REQUIRES IDP_URL and says so at START.
3
+ *
4
+ * There is no default in code. The quickstart generator writes the production sign-in service address into the app's .env.local
5
+ * at generation time; rigs override that file. When the value is missing, empty or not an absolute http(s) address, the server
6
+ * refuses to come up - printed once, in one fixed sentence, exit code 1 - instead of booting and failing on the first sign-in.
7
+ *
8
+ * A leaf module: no imports, so a generated `instrumentation.ts` can load it without pulling the session store or the logger.
9
+ */
10
+ export declare const IDP_URL_REQUIRED_SENTENCE = "IDP_URL is not set to a full http(s) address. This app has no default sign-in service: set IDP_URL in .env.local and start again.";
11
+ /** True when IDP_URL is present and absolute. Otherwise logs the fixed sentence and calls `exit(1)`; returns false if `exit` returns. */
12
+ export declare function requireIdpUrl(env?: Record<string, string | undefined>, exit?: (code: number) => unknown, log?: (line: string) => void): boolean;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IDP_URL_REQUIRED_SENTENCE = void 0;
4
+ exports.requireIdpUrl = requireIdpUrl;
5
+ /**
6
+ * 4.8.8 (item 24, Jon: "fast and loud are how i like my config errors"): the app REQUIRES IDP_URL and says so at START.
7
+ *
8
+ * There is no default in code. The quickstart generator writes the production sign-in service address into the app's .env.local
9
+ * at generation time; rigs override that file. When the value is missing, empty or not an absolute http(s) address, the server
10
+ * refuses to come up - printed once, in one fixed sentence, exit code 1 - instead of booting and failing on the first sign-in.
11
+ *
12
+ * A leaf module: no imports, so a generated `instrumentation.ts` can load it without pulling the session store or the logger.
13
+ */
14
+ exports.IDP_URL_REQUIRED_SENTENCE = 'IDP_URL is not set to a full http(s) address. This app has no default sign-in service: set IDP_URL in .env.local and start again.';
15
+ /** True when IDP_URL is present and absolute. Otherwise logs the fixed sentence and calls `exit(1)`; returns false if `exit` returns. */
16
+ function requireIdpUrl(env = process.env, exit = (code) => process.exit(code), log = (line) => console.error(line)) {
17
+ const value = (env.IDP_URL ?? '').trim();
18
+ if (/^https?:\/\/[^/\s]+/i.test(value))
19
+ return true;
20
+ log('');
21
+ log('[STARTUP FAILED - REFUSING TO SERVE]');
22
+ log(` ${exports.IDP_URL_REQUIRED_SENTENCE}`);
23
+ log('');
24
+ exit(1);
25
+ return false;
26
+ }
@@ -22,7 +22,7 @@ export { POST } from '../../api-handlers/account/masked-info';
22
22
  * email/phone options to the user.
23
23
  *
24
24
  * Environment variables used:
25
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
25
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
26
26
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
27
27
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
28
28
  *
@@ -28,7 +28,7 @@ Object.defineProperty(exports, "POST", { enumerable: true, get: function () { re
28
28
  * email/phone options to the user.
29
29
  *
30
30
  * Environment variables used:
31
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
31
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
32
32
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
33
33
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
34
34
  *
@@ -26,7 +26,7 @@ export { POST } from '../../api-handlers/account/send-code';
26
26
  * - contactId: string - ID of the masked contact to send to
27
27
  *
28
28
  * Environment variables used:
29
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
29
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
30
30
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
31
31
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
32
32
  *
@@ -31,7 +31,7 @@ Object.defineProperty(exports, "POST", { enumerable: true, get: function () { re
31
31
  * - contactId: string - ID of the masked contact to send to
32
32
  *
33
33
  * Environment variables used:
34
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
34
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
35
35
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
36
36
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
37
37
  *
@@ -25,7 +25,7 @@ export { POST } from '../../api-handlers/account/verify-email';
25
25
  * - emailId: string - ID of the email address used
26
26
  *
27
27
  * Environment variables used:
28
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
28
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
29
29
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
30
30
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
31
31
  *
@@ -30,7 +30,7 @@ Object.defineProperty(exports, "POST", { enumerable: true, get: function () { re
30
30
  * - emailId: string - ID of the email address used
31
31
  *
32
32
  * Environment variables used:
33
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
33
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
34
34
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
35
35
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
36
36
  *
@@ -25,7 +25,7 @@ export { POST } from '../../api-handlers/account/verify-sms';
25
25
  * - phoneId: string - ID of the phone number used
26
26
  *
27
27
  * Environment variables used:
28
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
28
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
29
29
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
30
30
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
31
31
  *
@@ -30,7 +30,7 @@ Object.defineProperty(exports, "POST", { enumerable: true, get: function () { re
30
30
  * - phoneId: string - ID of the phone number used
31
31
  *
32
32
  * Environment variables used:
33
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
33
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
34
34
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
35
35
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
36
36
  *
@@ -14,6 +14,9 @@
14
14
  * @since auth-ready-v2
15
15
  */
16
16
  import { NextRequest, NextResponse } from 'next/server';
17
+ /** 4.8.9: answered (status 500) when the server-side session delete failed. The browser's cookies are cleared either way. */
18
+ export declare const SESSION_NOT_ENDED_CODE = "SESSION_NOT_ENDED";
19
+ export declare const SESSION_NOT_ENDED_MESSAGE = "You were signed out on this device, but we could not end your session on the server. Try signing out again.";
17
20
  /**
18
21
  * POST /api/auth/logout - Sign out and clean up session
19
22
  *
@@ -15,6 +15,7 @@
15
15
  * @since auth-ready-v2
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.SESSION_NOT_ENDED_MESSAGE = exports.SESSION_NOT_ENDED_CODE = void 0;
18
19
  exports.POST = POST;
19
20
  const server_1 = require("next/server");
20
21
  const auth_1 = require("../../server/auth");
@@ -22,6 +23,9 @@ const session_store_1 = require("../../lib/session-store");
22
23
  const app_slug_1 = require("../../lib/app-slug");
23
24
  const site_logger_1 = require("../../lib/site-logger");
24
25
  const safe_error_response_1 = require("../../lib/safe-error-response");
26
+ /** 4.8.9: answered (status 500) when the server-side session delete failed. The browser's cookies are cleared either way. */
27
+ exports.SESSION_NOT_ENDED_CODE = 'SESSION_NOT_ENDED';
28
+ exports.SESSION_NOT_ENDED_MESSAGE = 'You were signed out on this device, but we could not end your session on the server. Try signing out again.';
25
29
  /**
26
30
  * POST /api/auth/logout - Sign out and clean up session
27
31
  *
@@ -41,6 +45,9 @@ async function POST(req) {
41
45
  });
42
46
  }
43
47
  const sessionId = session.session?.token;
48
+ // 4.8.9: a sign-out that did not end the session must not say it did. The delete below can fail (a faulting store); the
49
+ // cookies are still cleared, but the answer is then a typed failure instead of success:true.
50
+ let serverDeleteFailed = false;
44
51
  // Delete session from EVERY store — canonical + BA secondary (181922).
45
52
  // Canonical-only deletion let the ba: record resurrect the session.
46
53
  if (sessionId) {
@@ -49,7 +56,8 @@ async function POST(req) {
49
56
  console.info('[LOGOUT_ROUTE] Session deleted from store');
50
57
  }
51
58
  catch (error) {
52
- console.warn('[LOGOUT_ROUTE] Failed to delete session:', error);
59
+ serverDeleteFailed = true;
60
+ console.error('[LOGOUT_ROUTE] Failed to delete session - the server session is STILL LIVE:', error);
53
61
  }
54
62
  }
55
63
  // Log logout event (fire-and-forget)
@@ -65,10 +73,12 @@ async function POST(req) {
65
73
  });
66
74
  }
67
75
  // Build response that clears session cookies
68
- const response = server_1.NextResponse.json({
69
- success: true,
70
- message: 'Logged out successfully'
71
- });
76
+ const response = serverDeleteFailed
77
+ ? server_1.NextResponse.json({ success: false, code: exports.SESSION_NOT_ENDED_CODE, message: exports.SESSION_NOT_ENDED_MESSAGE }, { status: 500 })
78
+ : server_1.NextResponse.json({
79
+ success: true,
80
+ message: 'Logged out successfully'
81
+ });
72
82
  // Clear NextAuth session cookies (using app-slug prefixed names)
73
83
  const cookieNames = [
74
84
  (0, app_slug_1.getSessionCookieName)(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payez/next-mvp",
3
- "version": "4.8.7",
3
+ "version": "4.8.9",
4
4
  "sideEffects": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -179,6 +179,11 @@
179
179
  "require": "./dist/lib/api-handler.js",
180
180
  "default": "./dist/lib/api-handler.js"
181
181
  },
182
+ "./lib/require-idp-url": {
183
+ "types": "./dist/lib/require-idp-url.d.ts",
184
+ "require": "./dist/lib/require-idp-url.js",
185
+ "default": "./dist/lib/require-idp-url.js"
186
+ },
182
187
  "./lib/startup-init": {
183
188
  "types": "./dist/lib/startup-init.d.ts",
184
189
  "require": "./dist/lib/startup-init.js",
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 4.8.8 (item 24, Jon: "fast and loud are how i like my config errors"): the app REQUIRES IDP_URL and says so at START.
3
+ *
4
+ * There is no default in code. The quickstart generator writes the production sign-in service address into the app's .env.local
5
+ * at generation time; rigs override that file. When the value is missing, empty or not an absolute http(s) address, the server
6
+ * refuses to come up - printed once, in one fixed sentence, exit code 1 - instead of booting and failing on the first sign-in.
7
+ *
8
+ * A leaf module: no imports, so a generated `instrumentation.ts` can load it without pulling the session store or the logger.
9
+ */
10
+ export const IDP_URL_REQUIRED_SENTENCE =
11
+ 'IDP_URL is not set to a full http(s) address. This app has no default sign-in service: set IDP_URL in .env.local and start again.';
12
+
13
+ /** True when IDP_URL is present and absolute. Otherwise logs the fixed sentence and calls `exit(1)`; returns false if `exit` returns. */
14
+ export function requireIdpUrl(
15
+ env: Record<string, string | undefined> = process.env,
16
+ exit: (code: number) => unknown = (code) => process.exit(code),
17
+ log: (line: string) => void = (line) => console.error(line),
18
+ ): boolean {
19
+ const value = (env.IDP_URL ?? '').trim();
20
+ if (/^https?:\/\/[^/\s]+/i.test(value)) return true;
21
+ log('');
22
+ log('[STARTUP FAILED - REFUSING TO SERVE]');
23
+ log(` ${IDP_URL_REQUIRED_SENTENCE}`);
24
+ log('');
25
+ exit(1);
26
+ return false;
27
+ }
@@ -26,7 +26,7 @@ export { POST } from '../../api-handlers/account/masked-info';
26
26
  * email/phone options to the user.
27
27
  *
28
28
  * Environment variables used:
29
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
29
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
30
30
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
31
31
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
32
32
  *
@@ -29,7 +29,7 @@ export { POST } from '../../api-handlers/account/send-code';
29
29
  * - contactId: string - ID of the masked contact to send to
30
30
  *
31
31
  * Environment variables used:
32
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
32
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
33
33
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
34
34
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
35
35
  *
@@ -28,7 +28,7 @@ export { POST } from '../../api-handlers/account/verify-email';
28
28
  * - emailId: string - ID of the email address used
29
29
  *
30
30
  * Environment variables used:
31
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
31
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
32
32
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
33
33
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
34
34
  *
@@ -28,7 +28,7 @@ export { POST } from '../../api-handlers/account/verify-sms';
28
28
  * - phoneId: string - ID of the phone number used
29
29
  *
30
30
  * Environment variables used:
31
- * - IDP_URL or NEXT_PUBLIC_IDP_URL (default: http://localhost:32785)
31
+ * - IDP_URL or NEXT_PUBLIC_IDP_URL (REQUIRED: there is no default in code)
32
32
  * - CLIENT_ID or NEXT_PUBLIC_IDP_CLIENT_ID (required)
33
33
  * - BETTER_AUTH_SECRET (required — fetched from IDP at startup)
34
34
  *
@@ -27,6 +27,10 @@ import {
27
27
  import { siteEvents, getClientIp } from '../../lib/site-logger';
28
28
  import { logExceptionWithCorrelation } from '../../lib/safe-error-response';
29
29
 
30
+ /** 4.8.9: answered (status 500) when the server-side session delete failed. The browser's cookies are cleared either way. */
31
+ export const SESSION_NOT_ENDED_CODE = 'SESSION_NOT_ENDED';
32
+ export const SESSION_NOT_ENDED_MESSAGE = 'You were signed out on this device, but we could not end your session on the server. Try signing out again.';
33
+
30
34
  /**
31
35
  * POST /api/auth/logout - Sign out and clean up session
32
36
  *
@@ -48,6 +52,9 @@ export async function POST(req: NextRequest) {
48
52
  }
49
53
 
50
54
  const sessionId = session.session?.token;
55
+ // 4.8.9: a sign-out that did not end the session must not say it did. The delete below can fail (a faulting store); the
56
+ // cookies are still cleared, but the answer is then a typed failure instead of success:true.
57
+ let serverDeleteFailed = false;
51
58
 
52
59
  // Delete session from EVERY store — canonical + BA secondary (181922).
53
60
  // Canonical-only deletion let the ba: record resurrect the session.
@@ -56,7 +63,8 @@ export async function POST(req: NextRequest) {
56
63
  await deleteSessionEverywhere(sessionId);
57
64
  console.info('[LOGOUT_ROUTE] Session deleted from store');
58
65
  } catch (error) {
59
- console.warn('[LOGOUT_ROUTE] Failed to delete session:', error);
66
+ serverDeleteFailed = true;
67
+ console.error('[LOGOUT_ROUTE] Failed to delete session - the server session is STILL LIVE:', error);
60
68
  }
61
69
  }
62
70
 
@@ -74,10 +82,12 @@ export async function POST(req: NextRequest) {
74
82
  }
75
83
 
76
84
  // Build response that clears session cookies
77
- const response = NextResponse.json({
78
- success: true,
79
- message: 'Logged out successfully'
80
- });
85
+ const response = serverDeleteFailed
86
+ ? NextResponse.json({ success: false, code: SESSION_NOT_ENDED_CODE, message: SESSION_NOT_ENDED_MESSAGE }, { status: 500 })
87
+ : NextResponse.json({
88
+ success: true,
89
+ message: 'Logged out successfully'
90
+ });
81
91
 
82
92
  // Clear NextAuth session cookies (using app-slug prefixed names)
83
93
  const cookieNames = [