@grantry/nextjs 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +109 -2
- package/dist/cookie.d.ts +24 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cookie.js +24 -0
- package/dist/cookie.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +73 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +217 -0
- package/dist/middleware.js.map +1 -0
- package/dist/provider.d.ts +34 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +66 -0
- package/dist/provider.js.map +1 -0
- package/dist/server.d.ts +64 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +93 -0
- package/dist/server.js.map +1 -0
- package/package.json +61 -3
- package/src/cookie.ts +24 -0
- package/src/index.ts +249 -0
- package/src/middleware.ts +292 -0
- package/src/provider.tsx +73 -0
- package/src/server.ts +159 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The React provider, saying "nextjs" — and reading `NEXT_PUBLIC_GRANTRY_*`.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this file exists at all
|
|
5
|
+
*
|
|
6
|
+
* Everything else in this package is a straight re-export of `@grantry/react`,
|
|
7
|
+
* on the principle that a second implementation is a second thing to drift. So
|
|
8
|
+
* a wrapper needs a reason, and it has two:
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ A NEXT.JS APP AND A PLAIN REACT APP ARE INDISTINGUISHABLE ON THE WIRE
|
|
11
|
+
* UNLESS ONE OF THEM SAYS SO. They render the same provider, construct the same
|
|
12
|
+
* client and send the same requests. That is normally a virtue and here it is a
|
|
13
|
+
* problem: the development Overview's setup guide watches for the merchant's
|
|
14
|
+
* app to connect and then shows them how to sign a user up *in the framework
|
|
15
|
+
* that connected*. Without this, everyone who installed `@grantry/nextjs` would
|
|
16
|
+
* be shown a plain-React snippet — no `middleware.ts`, no server helpers — on
|
|
17
|
+
* the one screen whose whole job is knowing what they are building.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ AND IT IS NOT DETECTABLE AT RUNTIME. `__NEXT_DATA__` exists only in the
|
|
20
|
+
* Pages Router, `process.env.NEXT_RUNTIME` is server-only, and sniffing for
|
|
21
|
+
* either would make the answer depend on which router the merchant chose. The
|
|
22
|
+
* honest signal is which package they installed, and this is the file that
|
|
23
|
+
* knows.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ THE SECOND REASON IS THE ENVIRONMENT VARIABLES, WHICH ONLY THIS PACKAGE
|
|
26
|
+
* CAN READ. Where the application's pages are is its own code since 0242 —
|
|
27
|
+
* `signInUrl`, `signUpUrl`, the fallback and force redirect URLs and
|
|
28
|
+
* `afterSignOutUrl`, Clerk's names — and a Next.js application sets them as
|
|
29
|
+
* `NEXT_PUBLIC_GRANTRY_*`. They become props of the React provider here, under
|
|
30
|
+
* whatever props were passed, so the rules stay `@grantry/react`'s.
|
|
31
|
+
*/
|
|
32
|
+
import { type GrantryProviderProps } from '@grantry/react';
|
|
33
|
+
export declare function GrantryProvider(props: GrantryProviderProps): import("react").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,gBAAgB,CAAC;AAsBxB,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAW1D"}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* The React provider, saying "nextjs" — and reading `NEXT_PUBLIC_GRANTRY_*`.
|
|
5
|
+
*
|
|
6
|
+
* ## Why this file exists at all
|
|
7
|
+
*
|
|
8
|
+
* Everything else in this package is a straight re-export of `@grantry/react`,
|
|
9
|
+
* on the principle that a second implementation is a second thing to drift. So
|
|
10
|
+
* a wrapper needs a reason, and it has two:
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ A NEXT.JS APP AND A PLAIN REACT APP ARE INDISTINGUISHABLE ON THE WIRE
|
|
13
|
+
* UNLESS ONE OF THEM SAYS SO. They render the same provider, construct the same
|
|
14
|
+
* client and send the same requests. That is normally a virtue and here it is a
|
|
15
|
+
* problem: the development Overview's setup guide watches for the merchant's
|
|
16
|
+
* app to connect and then shows them how to sign a user up *in the framework
|
|
17
|
+
* that connected*. Without this, everyone who installed `@grantry/nextjs` would
|
|
18
|
+
* be shown a plain-React snippet — no `middleware.ts`, no server helpers — on
|
|
19
|
+
* the one screen whose whole job is knowing what they are building.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ AND IT IS NOT DETECTABLE AT RUNTIME. `__NEXT_DATA__` exists only in the
|
|
22
|
+
* Pages Router, `process.env.NEXT_RUNTIME` is server-only, and sniffing for
|
|
23
|
+
* either would make the answer depend on which router the merchant chose. The
|
|
24
|
+
* honest signal is which package they installed, and this is the file that
|
|
25
|
+
* knows.
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ THE SECOND REASON IS THE ENVIRONMENT VARIABLES, WHICH ONLY THIS PACKAGE
|
|
28
|
+
* CAN READ. Where the application's pages are is its own code since 0242 —
|
|
29
|
+
* `signInUrl`, `signUpUrl`, the fallback and force redirect URLs and
|
|
30
|
+
* `afterSignOutUrl`, Clerk's names — and a Next.js application sets them as
|
|
31
|
+
* `NEXT_PUBLIC_GRANTRY_*`. They become props of the React provider here, under
|
|
32
|
+
* whatever props were passed, so the rules stay `@grantry/react`'s.
|
|
33
|
+
*/
|
|
34
|
+
import { GrantryProvider as ReactProvider, mergeRouteOptions, } from '@grantry/react';
|
|
35
|
+
/**
|
|
36
|
+
* `NEXT_PUBLIC_GRANTRY_*`, as route options.
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ EVERY NAME WRITTEN OUT IN FULL. Next.js puts a `NEXT_PUBLIC_` variable into
|
|
39
|
+
* the browser bundle only where `process.env.NAME` appears literally; a name
|
|
40
|
+
* built from a prefix is correct on the server and `undefined` in the browser,
|
|
41
|
+
* which is a hydration mismatch as well as a wrong link.
|
|
42
|
+
*/
|
|
43
|
+
function publicRouteOptions() {
|
|
44
|
+
return {
|
|
45
|
+
signInUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_IN_URL,
|
|
46
|
+
signUpUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_UP_URL,
|
|
47
|
+
signInFallbackRedirectUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_IN_FALLBACK_REDIRECT_URL,
|
|
48
|
+
signUpFallbackRedirectUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_UP_FALLBACK_REDIRECT_URL,
|
|
49
|
+
signInForceRedirectUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_IN_FORCE_REDIRECT_URL,
|
|
50
|
+
signUpForceRedirectUrl: process.env.NEXT_PUBLIC_GRANTRY_SIGN_UP_FORCE_REDIRECT_URL,
|
|
51
|
+
afterSignOutUrl: process.env.NEXT_PUBLIC_GRANTRY_AFTER_SIGN_OUT_URL,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function GrantryProvider(props) {
|
|
55
|
+
/*
|
|
56
|
+
* Props first, then the variables — Clerk's order — merged option by option,
|
|
57
|
+
* so a prop left undefined does not hide the variable beneath it.
|
|
58
|
+
*
|
|
59
|
+
* `props.sdk` after `sdk="nextjs"`, so it is genuinely a default rather than
|
|
60
|
+
* a lock. An application embedding this inside something else can still say
|
|
61
|
+
* what it is, and passing nothing — the normal case — reports `nextjs`.
|
|
62
|
+
*/
|
|
63
|
+
const routes = mergeRouteOptions(props, publicRouteOptions());
|
|
64
|
+
return _jsx(ReactProvider, { sdk: "nextjs", ...props, ...routes });
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EACL,eAAe,IAAI,aAAa,EAChC,iBAAiB,GAGlB,MAAM,gBAAgB,CAAC;AAExB;;;;;;;GAOG;AACH,SAAS,kBAAkB;IACzB,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,+BAA+B;QACtD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,+BAA+B;QACtD,yBAAyB,EAAE,OAAO,CAAC,GAAG,CAAC,iDAAiD;QACxF,yBAAyB,EAAE,OAAO,CAAC,GAAG,CAAC,iDAAiD;QACxF,sBAAsB,EAAE,OAAO,CAAC,GAAG,CAAC,8CAA8C;QAClF,sBAAsB,EAAE,OAAO,CAAC,GAAG,CAAC,8CAA8C;QAClF,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,sCAAsC;KACpE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD;;;;;;;OAOG;IACH,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9D,OAAO,KAAC,aAAa,IAAC,GAAG,EAAC,QAAQ,KAAK,KAAK,KAAM,MAAM,GAAI,CAAC;AAC/D,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { TokenVerificationError, verifyToken, type VerifiedToken, type VerifyFailureReason, type VerifyTokenOptions } from '@grantry/react/server';
|
|
2
|
+
export interface AuthObject {
|
|
3
|
+
/** Null when the visitor is not signed in. */
|
|
4
|
+
userId: string | null;
|
|
5
|
+
sessionId: string | null;
|
|
6
|
+
environmentId: string | null;
|
|
7
|
+
emailVerified: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* The operator, when this visitor is somebody from your team viewing as the user — the verified
|
|
10
|
+
* `act` claim. Refuse your own sensitive actions while it is set.
|
|
11
|
+
*/
|
|
12
|
+
actor: {
|
|
13
|
+
sub: string;
|
|
14
|
+
} | null;
|
|
15
|
+
/** The verified token, for callers that want the expiry or full claims. */
|
|
16
|
+
token: VerifiedToken | null;
|
|
17
|
+
/**
|
|
18
|
+
* Why authentication failed, when it did.
|
|
19
|
+
*
|
|
20
|
+
* `expired` in particular is worth distinguishing: it usually means the tab
|
|
21
|
+
* was asleep and the client will recover on its own, not that anything is
|
|
22
|
+
* misconfigured.
|
|
23
|
+
*/
|
|
24
|
+
reason: TokenVerificationError['reason'] | null;
|
|
25
|
+
}
|
|
26
|
+
export interface AuthOptions {
|
|
27
|
+
/** Override for self-hosted deployments and local development. */
|
|
28
|
+
apiUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Your publishable key — the same one the provider takes.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ IT IS WHAT SAYS WHICH APPLICATION A TOKEN MUST BELONG TO. Read from
|
|
33
|
+
* `GRANTRY_PUBLISHABLE_KEY`, then `NEXT_PUBLIC_GRANTRY_PUBLISHABLE_KEY` — the
|
|
34
|
+
* variable `<GrantryProvider>` is already given — when omitted, so the
|
|
35
|
+
* ordinary Next.js application passes nothing here. With no key and no
|
|
36
|
+
* `environmentId`, `auth()` throws `no_audience` rather than accepting a token
|
|
37
|
+
* minted for another application. See `@grantry/react/server`.
|
|
38
|
+
*/
|
|
39
|
+
publishableKey?: string;
|
|
40
|
+
/** The environment id, for a deployment that configures it directly. */
|
|
41
|
+
environmentId?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The current visitor, in a server component, route handler or server action.
|
|
45
|
+
*
|
|
46
|
+
* Never throws for an unauthenticated visitor — being signed out is an ordinary
|
|
47
|
+
* state, not an exception. The `reason` field carries the detail when a token
|
|
48
|
+
* was present but unusable.
|
|
49
|
+
*/
|
|
50
|
+
export declare function auth(options?: AuthOptions): Promise<AuthObject>;
|
|
51
|
+
/**
|
|
52
|
+
* `auth()`, but throws when nobody is signed in.
|
|
53
|
+
*
|
|
54
|
+
* For routes where being signed out is a bug rather than a case to handle —
|
|
55
|
+
* it narrows `userId` to a string, so the call site needs no null check and
|
|
56
|
+
* cannot forget one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function requireAuth(options?: AuthOptions): Promise<AuthObject & {
|
|
59
|
+
userId: string;
|
|
60
|
+
sessionId: string;
|
|
61
|
+
}>;
|
|
62
|
+
export { TokenVerificationError, verifyToken, type VerifiedToken, type VerifyFailureReason, type VerifyTokenOptions, };
|
|
63
|
+
export { ReverificationCheckError, verifyReverification, type VerifiedReverification, } from '@grantry/react/server';
|
|
64
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAkBA,OAAO,EACL,sBAAsB,EACtB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9B,2EAA2E;IAC3E,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B;;;;;;OAMG;IACH,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;CACjD;AAYD,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA6BzE;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAW7D;AAMD,OAAO,EACL,sBAAsB,EACtB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,CAAC;AAMF,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side auth for the Next.js App Router — `@grantry/nextjs/server`.
|
|
3
|
+
*
|
|
4
|
+
* const { userId } = await auth();
|
|
5
|
+
* if (!userId) redirect('/sign-in');
|
|
6
|
+
*
|
|
7
|
+
* Reads the access-token cookie the client SDK mirrors onto this app's domain
|
|
8
|
+
* and verifies it against Grantry's published keys. No network call in the hot
|
|
9
|
+
* path once the keys are cached, so this is cheap enough to use on every render.
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ `auth()` IS THE APP ROUTER'S ALONE, SO THE VERIFIER IS EXPORTED BESIDE IT. It reads
|
|
12
|
+
* `next/headers`, which a Pages Router `getServerSideProps` or API route cannot call; those verify
|
|
13
|
+
* `req.cookies.grantry_token` with `verifyToken` themselves. The docs said "verify in
|
|
14
|
+
* getServerSideProps" while this entry point offered nothing to verify with, and a Next.js
|
|
15
|
+
* application is told not to install `@grantry/react` beside this package (the docs audit,
|
|
16
|
+
* 1 October 2026).
|
|
17
|
+
*/
|
|
18
|
+
import { cookies } from 'next/headers';
|
|
19
|
+
import { TokenVerificationError, verifyToken, } from '@grantry/react/server';
|
|
20
|
+
import { TOKEN_COOKIE_NAME, tokenFromCookie } from './cookie.js';
|
|
21
|
+
const SIGNED_OUT = {
|
|
22
|
+
userId: null,
|
|
23
|
+
sessionId: null,
|
|
24
|
+
environmentId: null,
|
|
25
|
+
emailVerified: false,
|
|
26
|
+
actor: null,
|
|
27
|
+
token: null,
|
|
28
|
+
reason: null,
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The current visitor, in a server component, route handler or server action.
|
|
32
|
+
*
|
|
33
|
+
* Never throws for an unauthenticated visitor — being signed out is an ordinary
|
|
34
|
+
* state, not an exception. The `reason` field carries the detail when a token
|
|
35
|
+
* was present but unusable.
|
|
36
|
+
*/
|
|
37
|
+
export async function auth(options = {}) {
|
|
38
|
+
const store = await cookies();
|
|
39
|
+
const raw = tokenFromCookie(store.get(TOKEN_COOKIE_NAME));
|
|
40
|
+
if (!raw)
|
|
41
|
+
return SIGNED_OUT;
|
|
42
|
+
try {
|
|
43
|
+
const token = await verifyToken(raw, options);
|
|
44
|
+
return {
|
|
45
|
+
userId: token.userId,
|
|
46
|
+
sessionId: token.sessionId,
|
|
47
|
+
environmentId: token.environmentId,
|
|
48
|
+
emailVerified: token.emailVerified,
|
|
49
|
+
actor: token.actor,
|
|
50
|
+
token,
|
|
51
|
+
reason: null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (cause) {
|
|
55
|
+
/*
|
|
56
|
+
* ⚠️ `no_audience` IS RETHROWN RATHER THAN REPORTED AS SIGNED OUT. It says
|
|
57
|
+
* this deployment never configured which application to accept tokens for,
|
|
58
|
+
* which is a fault in the app rather than a fact about the visitor —
|
|
59
|
+
* swallowing it would sign every user out at once and look like a broken
|
|
60
|
+
* cookie.
|
|
61
|
+
*/
|
|
62
|
+
if (cause instanceof TokenVerificationError && cause.reason !== 'no_audience') {
|
|
63
|
+
return { ...SIGNED_OUT, reason: cause.reason };
|
|
64
|
+
}
|
|
65
|
+
throw cause;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* `auth()`, but throws when nobody is signed in.
|
|
70
|
+
*
|
|
71
|
+
* For routes where being signed out is a bug rather than a case to handle —
|
|
72
|
+
* it narrows `userId` to a string, so the call site needs no null check and
|
|
73
|
+
* cannot forget one.
|
|
74
|
+
*/
|
|
75
|
+
export async function requireAuth(options = {}) {
|
|
76
|
+
const result = await auth(options);
|
|
77
|
+
if (!result.userId || !result.sessionId) {
|
|
78
|
+
throw new Error('requireAuth() was called but no user is signed in. ' +
|
|
79
|
+
'Use auth() and handle the signed-out case, or protect this route in middleware.');
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
/*
|
|
84
|
+
* `verifyToken` for the Pages Router and anything else `auth()` cannot reach — see the note at the
|
|
85
|
+
* top. The same function `@grantry/react/server` exports, not a copy.
|
|
86
|
+
*/
|
|
87
|
+
export { TokenVerificationError, verifyToken, };
|
|
88
|
+
/*
|
|
89
|
+
* Spending a "Verify it's you" grant before a sensitive action (0330), with `auth()`'s `sessionId`
|
|
90
|
+
* and `userId` — the same function `@grantry/react/server` exports, for the reason above.
|
|
91
|
+
*/
|
|
92
|
+
export { ReverificationCheckError, verifyReverification, } from '@grantry/react/server';
|
|
93
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EACL,sBAAsB,EACtB,WAAW,GAIZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAyB9D,MAAM,UAAU,GAAe;IAC7B,MAAM,EAAE,IAAI;IACZ,SAAS,EAAE,IAAI;IACf,aAAa,EAAE,IAAI;IACnB,aAAa,EAAE,KAAK;IACpB,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;CACb,CAAC;AAoBF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,UAAuB,EAAE;IAClD,MAAM,KAAK,GAAG,MAAM,OAAO,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,GAAG;QAAE,OAAO,UAAU,CAAC;IAE5B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,KAAK;YACL,MAAM,EAAE,IAAI;SACb,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf;;;;;;WAMG;QACH,IAAI,KAAK,YAAY,sBAAsB,IAAI,KAAK,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAC9E,OAAO,EAAE,GAAG,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QACjD,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,UAAuB,EAAE;IAEzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;IAEnC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,qDAAqD;YACnD,iFAAiF,CACpF,CAAC;IACJ,CAAC;IAED,OAAO,MAA4D,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,OAAO,EACL,sBAAsB,EACtB,WAAW,GAIZ,CAAC;AAEF;;;GAGG;AACH,OAAO,EACL,wBAAwB,EACxB,oBAAoB,GAErB,MAAM,uBAAuB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grantry/nextjs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Grantry authentication for Next.js: the React components and hooks, server-side auth() and route-protecting middleware.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"grantry",
|
|
9
|
+
"nextjs",
|
|
10
|
+
"next",
|
|
11
|
+
"authentication",
|
|
12
|
+
"auth",
|
|
13
|
+
"middleware",
|
|
14
|
+
"react"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://grantry.dev",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18.18.0"
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./server": {
|
|
27
|
+
"types": "./dist/server.d.ts",
|
|
28
|
+
"default": "./dist/server.js"
|
|
29
|
+
},
|
|
30
|
+
"./middleware": {
|
|
31
|
+
"types": "./dist/middleware.d.ts",
|
|
32
|
+
"default": "./dist/middleware.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"src",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@grantry/react": "0.1.1"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"next": "^15.0.0 || ^16.0.0",
|
|
50
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/react": "^19.0.2",
|
|
54
|
+
"next": "^16.3.1",
|
|
55
|
+
"react": "^19.2.8",
|
|
56
|
+
"typescript": "5.9.3"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "node build.mjs",
|
|
60
|
+
"typecheck": "tsc --noEmit",
|
|
61
|
+
"test": "node ../sdk-react/node_modules/vitest/vitest.mjs run"
|
|
62
|
+
},
|
|
63
|
+
"types": "./dist/index.d.ts"
|
|
6
64
|
}
|
package/src/cookie.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The access-token cookie, as the middleware and `auth()` both read it.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ ONE COPY OF THE NAME, AND A TEST HOLDS IT TO `@grantry/js`. It cannot be imported from there:
|
|
5
|
+
* this package depends on `@grantry/react` alone, and `@grantry/react`'s main entry is the browser
|
|
6
|
+
* half, which has no business in a middleware bundle. `server.ts` and `middleware.ts` each carried
|
|
7
|
+
* their own literal, one under a comment saying it was imported; `tests/cookie.test.ts` compares
|
|
8
|
+
* this one with the writer's.
|
|
9
|
+
*/
|
|
10
|
+
export const TOKEN_COOKIE_NAME = 'grantry_token';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The token in the cookie as Next.js hands it over, or null when there is none.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ NOT DECODED A SECOND TIME. Next's cookie parser already applies `decodeURIComponent` to every
|
|
16
|
+
* value, and both readers applied it again. A JWT never contains `%`, so the second pass changed
|
|
17
|
+
* nothing for a real token — and threw `URIError` for a cookie whose decoded value held a stray
|
|
18
|
+
* `%E0`. In `auth()` that error was not a `TokenVerificationError`, so it was rethrown and every
|
|
19
|
+
* server component calling `auth()` failed to render for that visitor. Anything other than a token
|
|
20
|
+
* that is still left is reported by `verifyToken` as `malformed`.
|
|
21
|
+
*/
|
|
22
|
+
export function tokenFromCookie(cookie: { value: string } | undefined): string | null {
|
|
23
|
+
return cookie?.value || null;
|
|
24
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @grantry/nextjs — authentication for Next.js.
|
|
3
|
+
*
|
|
4
|
+
* app/layout.tsx <GrantryProvider> (client)
|
|
5
|
+
* app/page.tsx await auth() (server)
|
|
6
|
+
* middleware.ts grantryMiddleware() (edge)
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ A RE-EXPORT, NOT A SECOND IMPLEMENTATION. Everything below is the React
|
|
9
|
+
* SDK unchanged, so a Next.js application installs one dependency and imports
|
|
10
|
+
* the same names it would in any React app — and there is no second copy of a
|
|
11
|
+
* component to drift from the first. The Next-specific half is `./server` and
|
|
12
|
+
* `./middleware`, which is genuinely different code.
|
|
13
|
+
*
|
|
14
|
+
* The one exception is `GrantryProvider`, which is a four-line wrapper around
|
|
15
|
+
* the React one for the sole purpose of saying "nextjs" rather than "react" on
|
|
16
|
+
* the wire. See `./provider` for why that distinction has to exist at all.
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ EVERY NAME `@grantry/react` EXPORTS IS LISTED HERE, AND A TEST HOLDS IT TO
|
|
19
|
+
* THAT. The list is by name, with no `export *`, so for months each new React
|
|
20
|
+
* export was simply absent from Next.js:
|
|
21
|
+
* `AuthCard`, `OAuthConsent`, the localization contract and the entitlement
|
|
22
|
+
* helpers all had documentation pages telling Next.js readers to import them
|
|
23
|
+
* from here. `packages/sdk-react/tests/nextjs-exports.test.ts` compares the two
|
|
24
|
+
* lists, types included, and fails on the next one.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export { GrantryProvider } from './provider';
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
// Hooks
|
|
31
|
+
useAuth,
|
|
32
|
+
useUser,
|
|
33
|
+
useSession,
|
|
34
|
+
useAccounts,
|
|
35
|
+
useOAuth,
|
|
36
|
+
useGrantry,
|
|
37
|
+
useEntitlements,
|
|
38
|
+
readOAuthResult,
|
|
39
|
+
// The public plans, for pricing cards of your own
|
|
40
|
+
usePlans,
|
|
41
|
+
pricingCatalogue,
|
|
42
|
+
formatMoney,
|
|
43
|
+
|
|
44
|
+
// Where the application's own pages are: the provider's route options and their helpers.
|
|
45
|
+
useRoutes,
|
|
46
|
+
mergeRouteOptions,
|
|
47
|
+
redirectTarget,
|
|
48
|
+
requestedRedirectUrl,
|
|
49
|
+
|
|
50
|
+
// Headless flows
|
|
51
|
+
useSignInFlow,
|
|
52
|
+
useSignUpFlow,
|
|
53
|
+
|
|
54
|
+
// Entitlements, read straight off an access token
|
|
55
|
+
entitlementsFromToken,
|
|
56
|
+
actorFromToken,
|
|
57
|
+
checkEntitlements,
|
|
58
|
+
EMPTY_ENTITLEMENTS,
|
|
59
|
+
|
|
60
|
+
// Control
|
|
61
|
+
SignedIn,
|
|
62
|
+
SignedOut,
|
|
63
|
+
GrantryLoading,
|
|
64
|
+
GrantryLoaded,
|
|
65
|
+
/*
|
|
66
|
+
* ⚠️ THE CONNECTION PAIR, WHICH IS NOT THE SESSION PAIR. See the React
|
|
67
|
+
* package's `control.tsx` for why they are separate rather than four states
|
|
68
|
+
* of one machine the way Clerk's are.
|
|
69
|
+
*/
|
|
70
|
+
GrantryDegraded,
|
|
71
|
+
GrantryFailed,
|
|
72
|
+
useEnvironmentStatus,
|
|
73
|
+
Protect,
|
|
74
|
+
|
|
75
|
+
// Unstyled actions
|
|
76
|
+
SignOutButton,
|
|
77
|
+
useSignOutAction,
|
|
78
|
+
|
|
79
|
+
// Drop-in components. Cut from a release and back; see the React package's
|
|
80
|
+
// index for what changed on the way back in (the default look, not the code).
|
|
81
|
+
SignIn,
|
|
82
|
+
SignUp,
|
|
83
|
+
/*
|
|
84
|
+
* ⚠️ RE-EXPORTED BY NAME, SO A NEW COMPONENT IS UNREACHABLE FROM NEXT.JS
|
|
85
|
+
* UNTIL IT IS LISTED HERE. `<SignIn />` draws this itself when the account
|
|
86
|
+
* owes something, but an application that redirects somewhere other than its
|
|
87
|
+
* sign-in route has to render it there.
|
|
88
|
+
*/
|
|
89
|
+
CompleteAccount,
|
|
90
|
+
UserButton,
|
|
91
|
+
/*
|
|
92
|
+
* ⚠️ NOT A COMPONENT, AND IT HAS TO BE HERE ANYWAY. `<UserButton />` sends
|
|
93
|
+
* "Add account" to the sign-in page with a marker on the link, and a Next.js
|
|
94
|
+
* application cannot let that visitor past its own gate without this. The
|
|
95
|
+
* list above is by name, so an unlisted helper is the same as one that does
|
|
96
|
+
* not exist -- and the failure is a redirect loop in somebody else's app.
|
|
97
|
+
*/
|
|
98
|
+
isAddingAccount,
|
|
99
|
+
addAccountUrl,
|
|
100
|
+
ADD_ACCOUNT_PARAM,
|
|
101
|
+
/*
|
|
102
|
+
* ⚠️ FOUR COMPONENTS THAT WERE EXPORTED FROM REACT AND UNREACHABLE FROM HERE,
|
|
103
|
+
* which is the same as not existing for a Next.js application — this list is
|
|
104
|
+
* by name and there is no deep-import escape hatch.
|
|
105
|
+
*/
|
|
106
|
+
UserAvatar,
|
|
107
|
+
Waitlist,
|
|
108
|
+
ResetPassword,
|
|
109
|
+
SetupMfa,
|
|
110
|
+
UserProfile,
|
|
111
|
+
PricingTable,
|
|
112
|
+
CheckoutButton,
|
|
113
|
+
PlanDetailsButton,
|
|
114
|
+
SubscriptionDetailsButton,
|
|
115
|
+
ProductCard,
|
|
116
|
+
type ProductCardProps,
|
|
117
|
+
MenuItems,
|
|
118
|
+
CustomAction,
|
|
119
|
+
CustomLink,
|
|
120
|
+
CustomPage,
|
|
121
|
+
// The card itself, the consent card, "Continue as" and the support-visit banner.
|
|
122
|
+
AuthCard,
|
|
123
|
+
OAuthConsent,
|
|
124
|
+
ContinueAs,
|
|
125
|
+
ImpersonationBanner,
|
|
126
|
+
|
|
127
|
+
// Localization
|
|
128
|
+
LocalizationProvider,
|
|
129
|
+
useTranslate,
|
|
130
|
+
EN,
|
|
131
|
+
|
|
132
|
+
// Appearance
|
|
133
|
+
AppearanceProvider,
|
|
134
|
+
GRANTRY_STYLES,
|
|
135
|
+
useStylesheet,
|
|
136
|
+
variablesToStyle,
|
|
137
|
+
|
|
138
|
+
// The brand theme: what Hosted sign-in → Theme stores, the hosted screens' look
|
|
139
|
+
THEME_COLORS,
|
|
140
|
+
THEME_COLOR_LABELS,
|
|
141
|
+
THEME_FONTS,
|
|
142
|
+
THEME_LOGO_PLACEMENTS,
|
|
143
|
+
THEME_RADII,
|
|
144
|
+
THEME_SOCIAL_PLACEMENTS,
|
|
145
|
+
THEME_SOCIAL_VARIANTS,
|
|
146
|
+
brandThemeAppearance,
|
|
147
|
+
cleanBrandTheme,
|
|
148
|
+
themeColorScheme,
|
|
149
|
+
|
|
150
|
+
// Avatars, drawn as the components draw them
|
|
151
|
+
DEFAULT_AVATAR_COLORS,
|
|
152
|
+
avatarBackgroundCss,
|
|
153
|
+
avatarInitial,
|
|
154
|
+
avatarSeed,
|
|
155
|
+
|
|
156
|
+
// The client
|
|
157
|
+
GrantryClient,
|
|
158
|
+
GrantryError,
|
|
159
|
+
TOKEN_COOKIE_NAME,
|
|
160
|
+
|
|
161
|
+
// "Verify it's you" (0330): the dialog before a sensitive action, and what it resolves with.
|
|
162
|
+
useReverification,
|
|
163
|
+
type Reverify,
|
|
164
|
+
type ReverifyOptions,
|
|
165
|
+
type Reverification,
|
|
166
|
+
type ReverificationGrant,
|
|
167
|
+
type ReverificationMethod,
|
|
168
|
+
|
|
169
|
+
type GrantryProviderProps,
|
|
170
|
+
type GrantryPreview,
|
|
171
|
+
type SignInHostProviderProps,
|
|
172
|
+
type ResellerHostProviderProps,
|
|
173
|
+
type Routes,
|
|
174
|
+
type RouteOptions,
|
|
175
|
+
type RedirectTargets,
|
|
176
|
+
type TokenEntitlements,
|
|
177
|
+
type HasParams,
|
|
178
|
+
type UseEntitlementsReturn,
|
|
179
|
+
type SignInStatus,
|
|
180
|
+
type SignUpStatus,
|
|
181
|
+
type ProtectProps,
|
|
182
|
+
type AuthCardProps,
|
|
183
|
+
type OAuthConsentProps,
|
|
184
|
+
type ContinueAsProps,
|
|
185
|
+
type Localization,
|
|
186
|
+
type LocalizationInput,
|
|
187
|
+
type GrantryClientOptions,
|
|
188
|
+
type SignUpParams,
|
|
189
|
+
type SignUpResult,
|
|
190
|
+
type BackupCodesProof,
|
|
191
|
+
type BackupCodeEntry,
|
|
192
|
+
type RevealedBackupCodes,
|
|
193
|
+
type ChangeQuote,
|
|
194
|
+
type CouponPreview,
|
|
195
|
+
type FieldIssue,
|
|
196
|
+
type BrandTheme,
|
|
197
|
+
type ThemeColor,
|
|
198
|
+
type ThemeFont,
|
|
199
|
+
type ThemeProblem,
|
|
200
|
+
type ThemeRadius,
|
|
201
|
+
type AvatarStyle,
|
|
202
|
+
type AvatarBackground,
|
|
203
|
+
type AvatarForeground,
|
|
204
|
+
type GrantryUser,
|
|
205
|
+
type GrantrySession,
|
|
206
|
+
type GrantryTrustedDevice,
|
|
207
|
+
type GrantryBoundDevice,
|
|
208
|
+
type GrantryBoundDevices,
|
|
209
|
+
type GrantryAccount,
|
|
210
|
+
type GrantryEnvironment,
|
|
211
|
+
type GrantryErrorCode,
|
|
212
|
+
type SignInResult,
|
|
213
|
+
type Appearance,
|
|
214
|
+
type ElementDescriptor,
|
|
215
|
+
type ElementsConfig,
|
|
216
|
+
type ElementStyle,
|
|
217
|
+
type AppearanceVariables,
|
|
218
|
+
type AppearanceLayout,
|
|
219
|
+
type EnvironmentStatus,
|
|
220
|
+
type SignOutButtonProps,
|
|
221
|
+
type UseSignOutAction,
|
|
222
|
+
type SignInProps,
|
|
223
|
+
type SignUpProps,
|
|
224
|
+
type CompleteAccountProps,
|
|
225
|
+
type UserButtonProps,
|
|
226
|
+
type UserProfileProps,
|
|
227
|
+
type UseAuthReturn,
|
|
228
|
+
type UseUserReturn,
|
|
229
|
+
type UseSessionReturn,
|
|
230
|
+
type UseAccountsReturn,
|
|
231
|
+
type UseOAuthReturn,
|
|
232
|
+
type UseSignInFlow,
|
|
233
|
+
type UseSignUpFlow,
|
|
234
|
+
type OAuthProviderInfo,
|
|
235
|
+
type ConnectedAccount,
|
|
236
|
+
type GrantryAuthorizedApp,
|
|
237
|
+
type MfaFactor,
|
|
238
|
+
type UserEmailAddress,
|
|
239
|
+
type PublicPlan,
|
|
240
|
+
type PlanPrice,
|
|
241
|
+
type PlansResponse,
|
|
242
|
+
type BillingSummary,
|
|
243
|
+
type UsePlansOptions,
|
|
244
|
+
type UsePlansReturn,
|
|
245
|
+
type PricingCatalogue,
|
|
246
|
+
type PricingPlan,
|
|
247
|
+
type PricingPlanPrice,
|
|
248
|
+
type PlanIntervalUnit,
|
|
249
|
+
} from '@grantry/react';
|