@workos-inc/authkit-nextjs 3.0.0-beta.1 → 3.0.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/README.md +305 -102
- package/dist/esm/actions.js +35 -5
- package/dist/esm/actions.js.map +1 -1
- package/dist/esm/auth.js +71 -21
- package/dist/esm/auth.js.map +1 -1
- package/dist/esm/authkit-callback-route.js +90 -92
- package/dist/esm/authkit-callback-route.js.map +1 -1
- package/dist/esm/components/authkit-provider.js +36 -15
- package/dist/esm/components/authkit-provider.js.map +1 -1
- package/dist/esm/components/impersonation.js +17 -15
- package/dist/esm/components/impersonation.js.map +1 -1
- package/dist/esm/components/min-max-button.js +1 -1
- package/dist/esm/components/min-max-button.js.map +1 -1
- package/dist/esm/components/tokenStore.js +28 -19
- package/dist/esm/components/tokenStore.js.map +1 -1
- package/dist/esm/components/useAccessToken.js +1 -1
- package/dist/esm/components/useAccessToken.js.map +1 -1
- package/dist/esm/components/useTokenClaims.js +1 -1
- package/dist/esm/components/useTokenClaims.js.map +1 -1
- package/dist/esm/cookie.js +20 -5
- package/dist/esm/cookie.js.map +1 -1
- package/dist/esm/env-variables.js +6 -6
- package/dist/esm/env-variables.js.map +1 -1
- package/dist/esm/errors.js +36 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/get-authorization-url.js +51 -12
- package/dist/esm/get-authorization-url.js.map +1 -1
- package/dist/esm/index.js +5 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interfaces.js +7 -1
- package/dist/esm/interfaces.js.map +1 -1
- package/dist/esm/middleware-helpers.js +102 -0
- package/dist/esm/middleware-helpers.js.map +1 -0
- package/dist/esm/middleware.js +3 -1
- package/dist/esm/middleware.js.map +1 -1
- package/dist/esm/pkce.js +52 -0
- package/dist/esm/pkce.js.map +1 -0
- package/dist/esm/session.js +82 -35
- package/dist/esm/session.js.map +1 -1
- package/dist/esm/test-helpers.js +1 -1
- package/dist/esm/test-helpers.js.map +1 -1
- package/dist/esm/types/actions.d.ts +34 -5
- package/dist/esm/types/auth.d.ts +7 -15
- package/dist/esm/types/components/authkit-provider.d.ts +6 -2
- package/dist/esm/types/components/impersonation.d.ts +2 -1
- package/dist/esm/types/cookie.d.ts +9 -0
- package/dist/esm/types/env-variables.d.ts +2 -1
- package/dist/esm/types/errors.d.ts +15 -0
- package/dist/esm/types/get-authorization-url.d.ts +2 -2
- package/dist/esm/types/index.d.ts +5 -2
- package/dist/esm/types/interfaces.d.ts +12 -0
- package/dist/esm/types/jwt.d.ts +9 -9
- package/dist/esm/types/middleware-helpers.d.ts +27 -0
- package/dist/esm/types/middleware.d.ts +3 -1
- package/dist/esm/types/pkce.d.ts +17 -0
- package/dist/esm/types/session.d.ts +1 -1
- package/dist/esm/types/utils.d.ts +5 -0
- package/dist/esm/types/validate-api-key.d.ts +1 -0
- package/dist/esm/types/workos.d.ts +1 -1
- package/dist/esm/utils.js +10 -2
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/validate-api-key.js +16 -0
- package/dist/esm/validate-api-key.js.map +1 -0
- package/dist/esm/workos.js +1 -1
- package/package.json +33 -34
- package/src/actions.spec.ts +91 -18
- package/src/actions.ts +44 -6
- package/src/auth.spec.ts +79 -29
- package/src/auth.ts +74 -42
- package/src/authkit-callback-route.spec.ts +372 -58
- package/src/authkit-callback-route.ts +121 -103
- package/src/components/authkit-provider.spec.tsx +264 -70
- package/src/components/authkit-provider.tsx +40 -15
- package/src/components/button.spec.tsx +4 -6
- package/src/components/impersonation.spec.tsx +152 -35
- package/src/components/impersonation.tsx +37 -30
- package/src/components/min-max-button.spec.tsx +2 -1
- package/src/components/tokenStore.spec.ts +59 -44
- package/src/components/tokenStore.ts +11 -3
- package/src/components/useAccessToken.spec.tsx +82 -83
- package/src/components/useTokenClaims.spec.tsx +23 -22
- package/src/cookie.spec.ts +63 -9
- package/src/cookie.ts +35 -0
- package/src/env-variables.ts +2 -0
- package/src/errors.spec.ts +108 -0
- package/src/errors.ts +46 -0
- package/src/get-authorization-url.spec.ts +170 -15
- package/src/get-authorization-url.ts +69 -23
- package/src/index.ts +20 -2
- package/src/interfaces.ts +15 -0
- package/src/jwt.ts +9 -9
- package/src/middleware-helpers.spec.ts +238 -0
- package/src/middleware-helpers.ts +134 -0
- package/src/middleware.spec.ts +25 -0
- package/src/middleware.ts +4 -1
- package/src/pkce.spec.ts +146 -0
- package/src/pkce.ts +59 -0
- package/src/session.spec.ts +87 -89
- package/src/session.ts +104 -27
- package/src/test-helpers.ts +1 -1
- package/src/utils.spec.ts +14 -31
- package/src/utils.ts +9 -0
- package/src/validate-api-key.spec.ts +111 -0
- package/src/validate-api-key.ts +19 -0
- package/src/workos.spec.ts +2 -2
- package/src/workos.ts +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
/** Internal AuthKit headers - forwarded to downstream requests but never sent to browser. */
|
|
3
|
+
export declare const AUTHKIT_REQUEST_HEADERS: readonly ["x-workos-middleware", "x-url", "x-redirect-uri", "x-sign-up-paths", "x-workos-session"];
|
|
4
|
+
export type AuthkitRequestHeader = (typeof AUTHKIT_REQUEST_HEADERS)[number];
|
|
5
|
+
export declare function isAuthkitRequestHeader(name: string): boolean;
|
|
6
|
+
export interface AuthkitHeadersResult {
|
|
7
|
+
requestHeaders: Headers;
|
|
8
|
+
responseHeaders: Headers;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Partitions AuthKit headers into request headers (for withAuth) and response headers (for browser).
|
|
12
|
+
*/
|
|
13
|
+
export declare function partitionAuthkitHeaders(request: NextRequest, authkitHeaders: Headers): AuthkitHeadersResult;
|
|
14
|
+
export declare function applyResponseHeaders(response: NextResponse, responseHeaders: Headers): NextResponse;
|
|
15
|
+
export type AuthkitRedirectStatus = 302 | 303 | 307 | 308;
|
|
16
|
+
export interface HandleAuthkitHeadersOptions {
|
|
17
|
+
/** URL to redirect to (relative or absolute). */
|
|
18
|
+
redirect?: string | URL;
|
|
19
|
+
/** Redirect status code. @default 307 for GET/HEAD, 303 for POST/PUT/DELETE */
|
|
20
|
+
redirectStatus?: AuthkitRedirectStatus;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Creates a NextResponse with properly merged AuthKit headers.
|
|
24
|
+
*/
|
|
25
|
+
export declare function handleAuthkitProxy(request: NextRequest, authkitHeaders: Headers, options?: HandleAuthkitHeadersOptions): NextResponse;
|
|
26
|
+
/** @deprecated Use `handleAuthkitProxy` instead. */
|
|
27
|
+
export declare const handleAuthkitHeaders: typeof handleAuthkitProxy;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { NextMiddleware, NextRequest } from 'next/server';
|
|
2
2
|
import { AuthkitMiddlewareOptions, AuthkitOptions, AuthkitResponse } from './interfaces.js';
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function authkitProxy({ debug, middlewareAuth, redirectUri, signUpPaths, eagerAuth, }?: AuthkitMiddlewareOptions): NextMiddleware;
|
|
4
|
+
/** @deprecated Use `authkitProxy` instead. */
|
|
5
|
+
export declare const authkitMiddleware: typeof authkitProxy;
|
|
4
6
|
export declare function authkit(request: NextRequest, options?: AuthkitOptions): Promise<AuthkitResponse>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { State } from './interfaces.js';
|
|
2
|
+
export declare const PKCE_COOKIE_NAME = "wos-auth-verifier";
|
|
3
|
+
/**
|
|
4
|
+
* Derive a flow-specific cookie name so concurrent auth flows don't overwrite
|
|
5
|
+
* each other's PKCE cookies. Uses an FNV-1a hash of the full sealed state
|
|
6
|
+
*/
|
|
7
|
+
export declare function getPKCECookieNameForState(state: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Set the PKCE verifier cookie in server action context.
|
|
10
|
+
* In middleware context, callers must set the cookie via Set-Cookie headers instead.
|
|
11
|
+
*/
|
|
12
|
+
export declare function setPKCECookie(sealedState: string): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Read and unseal the auth cookie containing PKCE code verifier and OAuth state.
|
|
15
|
+
* Throws if the cookie is not in the required state
|
|
16
|
+
*/
|
|
17
|
+
export declare function getStateFromPKCECookieValue(cookieValue: string): Promise<State>;
|
|
@@ -3,7 +3,7 @@ import { NextRequest } from 'next/server';
|
|
|
3
3
|
import { AuthkitMiddlewareAuth, AuthkitOptions, AuthkitResponse, NoUserInfo, Session, UserInfo } from './interfaces.js';
|
|
4
4
|
import type { AuthenticationResponse } from '@workos-inc/node';
|
|
5
5
|
declare function encryptSession(session: Session): Promise<string>;
|
|
6
|
-
declare function updateSessionMiddleware(request: NextRequest, debug: boolean, middlewareAuth: AuthkitMiddlewareAuth, redirectUri: string, signUpPaths: string[], eagerAuth?: boolean): Promise<
|
|
6
|
+
declare function updateSessionMiddleware(request: NextRequest, debug: boolean, middlewareAuth: AuthkitMiddlewareAuth, redirectUri: string, signUpPaths: string[], eagerAuth?: boolean): Promise<import("next/server.js").NextResponse<unknown>>;
|
|
7
7
|
declare function updateSession(request: NextRequest, options?: AuthkitOptions): Promise<AuthkitResponse>;
|
|
8
8
|
declare function refreshSession(options: {
|
|
9
9
|
organizationId?: string;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets cache prevention headers to prevent CDN/proxy caching.
|
|
3
|
+
* @param headers - The Headers object to set the cache prevention headers on.
|
|
4
|
+
*/
|
|
5
|
+
export declare function setCachePreventionHeaders(headers: Headers): void;
|
|
1
6
|
export declare function redirectWithFallback(redirectUri: string, headers?: Headers): Response;
|
|
2
7
|
export declare function errorResponseWithFallback(errorBody: {
|
|
3
8
|
error: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validateApiKey(): Promise<import("@workos-inc/node").ValidateApiKeyResponse>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WorkOS } from '@workos-inc/node';
|
|
2
|
-
export declare const VERSION = "2.
|
|
2
|
+
export declare const VERSION = "2.14.0";
|
|
3
3
|
/**
|
|
4
4
|
* Create a WorkOS instance with the provided API key and options.
|
|
5
5
|
* If an instance already exists, it returns the existing instance.
|
package/dist/esm/utils.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
+
/**
|
|
3
|
+
* Sets cache prevention headers to prevent CDN/proxy caching.
|
|
4
|
+
* @param headers - The Headers object to set the cache prevention headers on.
|
|
5
|
+
*/
|
|
6
|
+
export function setCachePreventionHeaders(headers) {
|
|
7
|
+
headers.set('Cache-Control', 'private, no-cache, no-store, must-revalidate, max-age=0');
|
|
8
|
+
headers.set('x-middleware-cache', 'no-cache');
|
|
9
|
+
}
|
|
2
10
|
export function redirectWithFallback(redirectUri, headers) {
|
|
3
11
|
const newHeaders = headers ? new Headers(headers) : new Headers();
|
|
4
12
|
newHeaders.set('Location', redirectUri);
|
|
5
13
|
// Fall back to standard Response if NextResponse is not available.
|
|
6
14
|
// This is to support Next.js 13.
|
|
7
|
-
return
|
|
15
|
+
return NextResponse?.redirect
|
|
8
16
|
? NextResponse.redirect(redirectUri, { headers })
|
|
9
17
|
: new Response(null, { status: 307, headers: newHeaders });
|
|
10
18
|
}
|
|
11
19
|
export function errorResponseWithFallback(errorBody) {
|
|
12
20
|
// Fall back to standard Response if NextResponse is not available.
|
|
13
21
|
// This is to support Next.js 13.
|
|
14
|
-
return
|
|
22
|
+
return NextResponse?.json
|
|
15
23
|
? NextResponse.json(errorBody, { status: 500 })
|
|
16
24
|
: new Response(JSON.stringify(errorBody), {
|
|
17
25
|
status: 500,
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,UAAU,oBAAoB,CAAC,WAAmB,EAAE,OAAiB;IACzE,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;IAClE,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAExC,mEAAmE;IACnE,iCAAiC;IACjC,OAAO,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAAgB;IACxD,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,yDAAyD,CAAC,CAAC;IACxF,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,WAAmB,EAAE,OAAiB;IACzE,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;IAClE,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAExC,mEAAmE;IACnE,iCAAiC;IACjC,OAAO,YAAY,EAAE,QAAQ;QAC3B,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,CAAC;QACjD,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,SAA8D;IACtG,mEAAmE;IACnE,iCAAiC;IACjC,OAAO,YAAY,EAAE,IAAI;QACvB,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;QAC/C,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE;YACtC,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CAAC;AACT,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAAI,EAAW;IACjC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,MAAS,CAAC;IACd,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,EAAE,EAAE,CAAC;YACd,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
import { getWorkOS } from './workos.js';
|
|
3
|
+
import { headers } from 'next/headers';
|
|
4
|
+
export async function validateApiKey() {
|
|
5
|
+
const headersList = await headers();
|
|
6
|
+
const authorizationHeader = headersList.get('authorization');
|
|
7
|
+
if (!authorizationHeader) {
|
|
8
|
+
return { apiKey: null };
|
|
9
|
+
}
|
|
10
|
+
const value = authorizationHeader.match(/Bearer\s+(.*)/i)?.[1];
|
|
11
|
+
if (!value) {
|
|
12
|
+
return { apiKey: null };
|
|
13
|
+
}
|
|
14
|
+
return getWorkOS().apiKeys.validateApiKey({ value });
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=validate-api-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-api-key.js","sourceRoot":"","sources":["../../src/validate-api-key.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,WAAW,GAAG,MAAM,OAAO,EAAE,CAAC;IACpC,MAAM,mBAAmB,GAAG,WAAW,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7D,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC1B,CAAC;IAED,MAAM,KAAK,GAAG,mBAAmB,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC1B,CAAC;IAED,OAAO,SAAS,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACvD,CAAC"}
|
package/dist/esm/workos.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WorkOS } from '@workos-inc/node';
|
|
2
2
|
import { WORKOS_API_HOSTNAME, WORKOS_API_KEY, WORKOS_API_HTTPS, WORKOS_API_PORT } from './env-variables.js';
|
|
3
3
|
import { lazy } from './utils.js';
|
|
4
|
-
export const VERSION = '2.
|
|
4
|
+
export const VERSION = '2.14.0';
|
|
5
5
|
const options = {
|
|
6
6
|
apiHostname: WORKOS_API_HOSTNAME,
|
|
7
7
|
https: WORKOS_API_HTTPS ? WORKOS_API_HTTPS === 'true' : true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workos-inc/authkit-nextjs",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Authentication and session helpers for using WorkOS & AuthKit with Next.js",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -22,47 +22,34 @@
|
|
|
22
22
|
"import": "./dist/esm/index.js"
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
|
-
"scripts": {
|
|
26
|
-
"clean": "rm -rf dist",
|
|
27
|
-
"prebuild": "npm run clean",
|
|
28
|
-
"build": "tsc --project tsconfig.json",
|
|
29
|
-
"prepublishOnly": "npm run lint",
|
|
30
|
-
"lint": "eslint \"src/**/*.ts*\"",
|
|
31
|
-
"test": "jest",
|
|
32
|
-
"test:watch": "jest --watch",
|
|
33
|
-
"prettier": "prettier \"src/**/*.{js,ts,tsx}\" --check",
|
|
34
|
-
"format": "prettier \"src/**/*.{js,ts,tsx}\" --write",
|
|
35
|
-
"type-check": "tsc --project tsconfig.json --noEmit"
|
|
36
|
-
},
|
|
37
25
|
"dependencies": {
|
|
38
|
-
"@
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
26
|
+
"@sindresorhus/fnv1a": "^3.1.0",
|
|
27
|
+
"@workos-inc/node": "^8.9.0",
|
|
28
|
+
"iron-session": "^8.0.4",
|
|
29
|
+
"jose": "^5.10.0",
|
|
30
|
+
"path-to-regexp": "^6.3.0",
|
|
31
|
+
"valibot": "^1.2.0"
|
|
42
32
|
},
|
|
43
33
|
"peerDependencies": {
|
|
44
|
-
"next": "^13.5.9 || ^14.2.26 || ^15.2.3",
|
|
34
|
+
"next": "^13.5.9 || ^14.2.26 || ^15.2.3 || ^16",
|
|
45
35
|
"react": "^18.0 || ^19.0.0",
|
|
46
36
|
"react-dom": "^18.0 || ^19.0.0"
|
|
47
37
|
},
|
|
48
38
|
"devDependencies": {
|
|
49
|
-
"@testing-library/jest-dom": "^6.
|
|
50
|
-
"@testing-library/react": "^16.
|
|
51
|
-
"@types/
|
|
52
|
-
"@types/node": "^20.11.28",
|
|
39
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
40
|
+
"@testing-library/react": "^16.3.2",
|
|
41
|
+
"@types/node": "^20.19.35",
|
|
53
42
|
"@types/react": "18.2.67",
|
|
54
43
|
"@types/react-dom": "18.2.22",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"typescript": "5.4.2",
|
|
65
|
-
"typescript-eslint": "^7.2.0"
|
|
44
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
45
|
+
"jsdom": "^26.1.0",
|
|
46
|
+
"next": "^16.2.1",
|
|
47
|
+
"oxfmt": "^0.42.0",
|
|
48
|
+
"oxlint": "^1.57.0",
|
|
49
|
+
"tslib": "^2.8.1",
|
|
50
|
+
"typescript": "6.0.2",
|
|
51
|
+
"typescript-eslint": "^7.18.0",
|
|
52
|
+
"vitest": "^3.2.4"
|
|
66
53
|
},
|
|
67
54
|
"license": "MIT",
|
|
68
55
|
"homepage": "https://github.com/workos/authkit-nextjs#readme",
|
|
@@ -72,5 +59,17 @@
|
|
|
72
59
|
},
|
|
73
60
|
"bugs": {
|
|
74
61
|
"url": "https://github.com/workos/authkit-nextjs/issues"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"clean": "rm -rf dist",
|
|
65
|
+
"prebuild": "pnpm run clean",
|
|
66
|
+
"build": "tsc --project tsconfig.app.json",
|
|
67
|
+
"test": "vitest run",
|
|
68
|
+
"test:watch": "vitest",
|
|
69
|
+
"test:coverage": "vitest run --coverage",
|
|
70
|
+
"lint": "oxlint",
|
|
71
|
+
"format": "oxfmt .",
|
|
72
|
+
"format:check": "oxfmt --check .",
|
|
73
|
+
"typecheck": "tsc --project tsconfig.app.json --noEmit && tsc --project tsconfig.test.json --noEmit"
|
|
75
74
|
}
|
|
76
|
-
}
|
|
75
|
+
}
|
package/src/actions.spec.ts
CHANGED
|
@@ -8,31 +8,50 @@ import {
|
|
|
8
8
|
getAccessTokenAction,
|
|
9
9
|
refreshAccessTokenAction,
|
|
10
10
|
} from '../src/actions.js';
|
|
11
|
-
import { signOut, switchToOrganization } from './auth.js';
|
|
11
|
+
import { getSignInUrl, signOut, switchToOrganization } from './auth.js';
|
|
12
12
|
import { getWorkOS } from '../src/workos.js';
|
|
13
13
|
import { withAuth, refreshSession } from '../src/session.js';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
vi.mock('../src/auth.js', () => ({
|
|
16
|
+
getSignInUrl: vi.fn().mockResolvedValue('https://api.workos.com/authorize?...'),
|
|
17
|
+
signOut: vi.fn().mockResolvedValue(true),
|
|
18
|
+
switchToOrganization: vi.fn().mockResolvedValue({ organizationId: 'org_123' }),
|
|
18
19
|
}));
|
|
19
20
|
|
|
20
|
-
const fakeWorkosInstance = {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const { fakeWorkosInstance } = vi.hoisted(() => ({
|
|
22
|
+
fakeWorkosInstance: {
|
|
23
|
+
organizations: {
|
|
24
|
+
getOrganization: vi.fn().mockResolvedValue({ id: 'org_123', name: 'Test Org' }),
|
|
25
|
+
},
|
|
23
26
|
},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
getWorkOS:
|
|
27
|
+
}));
|
|
28
|
+
vi.mock('../src/workos.js', () => ({
|
|
29
|
+
getWorkOS: vi.fn(() => fakeWorkosInstance),
|
|
27
30
|
}));
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
withAuth:
|
|
31
|
-
refreshSession:
|
|
32
|
+
vi.mock('../src/session.js', () => ({
|
|
33
|
+
withAuth: vi.fn().mockResolvedValue({ user: 'testUser', accessToken: 'access_token' }),
|
|
34
|
+
refreshSession: vi.fn().mockResolvedValue({ user: 'testUser', accessToken: 'refreshed_token' }),
|
|
32
35
|
}));
|
|
33
36
|
|
|
34
37
|
describe('actions', () => {
|
|
35
38
|
const workos = getWorkOS();
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
vi.clearAllMocks();
|
|
42
|
+
// Restore default mock implementations
|
|
43
|
+
vi.mocked(withAuth).mockResolvedValue({
|
|
44
|
+
user: 'testUser' as never,
|
|
45
|
+
sessionId: 'session_123',
|
|
46
|
+
accessToken: 'access_token',
|
|
47
|
+
});
|
|
48
|
+
vi.mocked(refreshSession).mockResolvedValue({
|
|
49
|
+
user: 'testUser' as never,
|
|
50
|
+
sessionId: 'session_123',
|
|
51
|
+
accessToken: 'refreshed_token',
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
36
55
|
describe('checkSessionAction', () => {
|
|
37
56
|
it('should return true for authenticated users', async () => {
|
|
38
57
|
const result = await checkSessionAction();
|
|
@@ -60,16 +79,52 @@ describe('actions', () => {
|
|
|
60
79
|
it('should return auth details', async () => {
|
|
61
80
|
const result = await getAuthAction();
|
|
62
81
|
expect(withAuth).toHaveBeenCalled();
|
|
63
|
-
expect(result).toEqual({ user: 'testUser' });
|
|
82
|
+
expect(result).toEqual({ user: 'testUser', sessionId: 'session_123' });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should not pass ensureSignedIn to withAuth', async () => {
|
|
86
|
+
await getAuthAction({ ensureSignedIn: true });
|
|
87
|
+
expect(withAuth).toHaveBeenCalledWith();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should return signInUrl when ensureSignedIn is true and no user', async () => {
|
|
91
|
+
vi.mocked(withAuth).mockResolvedValueOnce({ user: null });
|
|
92
|
+
const result = await getAuthAction({ ensureSignedIn: true });
|
|
93
|
+
expect(getSignInUrl).toHaveBeenCalled();
|
|
94
|
+
expect(result).toEqual({ user: null, signInUrl: 'https://api.workos.com/authorize?...' });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should not return signInUrl when ensureSignedIn is true and user exists', async () => {
|
|
98
|
+
const result = await getAuthAction({ ensureSignedIn: true });
|
|
99
|
+
expect(getSignInUrl).not.toHaveBeenCalled();
|
|
100
|
+
expect(result).toEqual({ user: 'testUser', sessionId: 'session_123' });
|
|
64
101
|
});
|
|
65
102
|
});
|
|
66
103
|
|
|
67
104
|
describe('refreshAuthAction', () => {
|
|
68
105
|
it('should refresh session', async () => {
|
|
69
|
-
const params = { ensureSignedIn:
|
|
106
|
+
const params = { ensureSignedIn: false, organizationId: 'org_123' };
|
|
70
107
|
const result = await refreshAuthAction(params);
|
|
71
|
-
expect(refreshSession).toHaveBeenCalledWith(
|
|
72
|
-
expect(result).toEqual({
|
|
108
|
+
expect(refreshSession).toHaveBeenCalledWith({ organizationId: 'org_123' });
|
|
109
|
+
expect(result).toEqual({ user: 'testUser', sessionId: 'session_123' });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should not pass ensureSignedIn to refreshSession', async () => {
|
|
113
|
+
await refreshAuthAction({ ensureSignedIn: true, organizationId: 'org_123' });
|
|
114
|
+
expect(refreshSession).toHaveBeenCalledWith({ organizationId: 'org_123' });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should return signInUrl when ensureSignedIn is true and no user', async () => {
|
|
118
|
+
vi.mocked(refreshSession).mockResolvedValueOnce({ user: null });
|
|
119
|
+
const result = await refreshAuthAction({ ensureSignedIn: true });
|
|
120
|
+
expect(getSignInUrl).toHaveBeenCalled();
|
|
121
|
+
expect(result).toEqual({ user: null, signInUrl: 'https://api.workos.com/authorize?...' });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should not return signInUrl when ensureSignedIn is true and user exists', async () => {
|
|
125
|
+
const result = await refreshAuthAction({ ensureSignedIn: true });
|
|
126
|
+
expect(getSignInUrl).not.toHaveBeenCalled();
|
|
127
|
+
expect(result).toEqual({ user: 'testUser', sessionId: 'session_123' });
|
|
73
128
|
});
|
|
74
129
|
});
|
|
75
130
|
|
|
@@ -94,7 +149,25 @@ describe('actions', () => {
|
|
|
94
149
|
it('should refresh access token', async () => {
|
|
95
150
|
const result = await refreshAccessTokenAction();
|
|
96
151
|
expect(refreshSession).toHaveBeenCalled();
|
|
97
|
-
expect(result).toEqual('refreshed_token');
|
|
152
|
+
expect(result).toEqual({ accessToken: 'refreshed_token' });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should catch errors and return a generic error instead of throwing', async () => {
|
|
156
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
157
|
+
vi.mocked(refreshSession).mockRejectedValueOnce(new Error('Rate limit exceeded'));
|
|
158
|
+
const result = await refreshAccessTokenAction();
|
|
159
|
+
expect(result).toEqual({ accessToken: undefined, error: 'Failed to refresh access token' });
|
|
160
|
+
expect(warnSpy).toHaveBeenCalledWith('Failed to refresh access token:', 'Rate limit exceeded');
|
|
161
|
+
warnSpy.mockRestore();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should handle non-Error objects in catch', async () => {
|
|
165
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
166
|
+
vi.mocked(refreshSession).mockRejectedValueOnce('string error');
|
|
167
|
+
const result = await refreshAccessTokenAction();
|
|
168
|
+
expect(result).toEqual({ accessToken: undefined, error: 'Failed to refresh access token' });
|
|
169
|
+
expect(warnSpy).toHaveBeenCalledWith('Failed to refresh access token:', 'string error');
|
|
170
|
+
warnSpy.mockRestore();
|
|
98
171
|
});
|
|
99
172
|
});
|
|
100
173
|
});
|
package/src/actions.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
|
-
import { signOut, switchToOrganization } from './auth.js';
|
|
3
|
+
import { getSignInUrl, signOut, switchToOrganization } from './auth.js';
|
|
4
4
|
import { NoUserInfo, UserInfo, SwitchToOrganizationOptions } from './interfaces.js';
|
|
5
5
|
import { refreshSession, withAuth } from './session.js';
|
|
6
6
|
import { getWorkOS } from './workos.js';
|
|
7
7
|
|
|
8
|
+
export interface RefreshAccessTokenActionResult {
|
|
9
|
+
accessToken: string | undefined;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
/**
|
|
9
14
|
* This function is used to sanitize the auth object.
|
|
10
15
|
* Remove the accessToken from the auth object as it is not needed on the client side.
|
|
@@ -35,7 +40,19 @@ export const getOrganizationAction = async (organizationId: string) => {
|
|
|
35
40
|
};
|
|
36
41
|
|
|
37
42
|
export const getAuthAction = async (options?: { ensureSignedIn?: boolean }) => {
|
|
38
|
-
|
|
43
|
+
// Never pass ensureSignedIn to withAuth from a server action, because withAuth
|
|
44
|
+
// would call redirect() to an external URL, which causes CORS errors when
|
|
45
|
+
// invoked via a client-side fetch. Instead, return the sign-in URL so the
|
|
46
|
+
// client can redirect via window.location.href.
|
|
47
|
+
const auth = await withAuth();
|
|
48
|
+
const sanitized = sanitize(auth);
|
|
49
|
+
|
|
50
|
+
if (options?.ensureSignedIn && !auth.user) {
|
|
51
|
+
const signInUrl = await getSignInUrl();
|
|
52
|
+
return { ...sanitized, signInUrl };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return sanitized;
|
|
39
56
|
};
|
|
40
57
|
|
|
41
58
|
export const refreshAuthAction = async ({
|
|
@@ -45,7 +62,17 @@ export const refreshAuthAction = async ({
|
|
|
45
62
|
ensureSignedIn?: boolean;
|
|
46
63
|
organizationId?: string;
|
|
47
64
|
}) => {
|
|
48
|
-
|
|
65
|
+
// Never pass ensureSignedIn to refreshSession from a server action for the
|
|
66
|
+
// same CORS reason as getAuthAction above.
|
|
67
|
+
const auth = await refreshSession({ organizationId });
|
|
68
|
+
const sanitized = sanitize(auth);
|
|
69
|
+
|
|
70
|
+
if (ensureSignedIn && !auth.user) {
|
|
71
|
+
const signInUrl = await getSignInUrl();
|
|
72
|
+
return { ...sanitized, signInUrl };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return sanitized;
|
|
49
76
|
};
|
|
50
77
|
|
|
51
78
|
export const switchToOrganizationAction = async (organizationId: string, options?: SwitchToOrganizationOptions) => {
|
|
@@ -64,8 +91,19 @@ export async function getAccessTokenAction() {
|
|
|
64
91
|
/**
|
|
65
92
|
* This action is used to refresh the access token from the auth object.
|
|
66
93
|
* It is used to fetch the access token from the server.
|
|
94
|
+
*
|
|
95
|
+
* Errors are caught and returned as data rather than thrown, to prevent
|
|
96
|
+
* Next.js from returning 500 responses for server action failures.
|
|
67
97
|
*/
|
|
68
|
-
export async function refreshAccessTokenAction() {
|
|
69
|
-
|
|
70
|
-
|
|
98
|
+
export async function refreshAccessTokenAction(): Promise<RefreshAccessTokenActionResult> {
|
|
99
|
+
try {
|
|
100
|
+
const auth = await refreshSession();
|
|
101
|
+
return { accessToken: auth.accessToken };
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.warn('Failed to refresh access token:', error instanceof Error ? error.message : String(error));
|
|
104
|
+
return {
|
|
105
|
+
accessToken: undefined,
|
|
106
|
+
error: 'Failed to refresh access token',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
71
109
|
}
|