@spawn-llc/auth 0.3.0 → 0.3.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/CHANGELOG.md +15 -0
- package/IDENTITY.md +8 -5
- package/README.md +26 -6
- package/dist/{chunk-ELKYJ3OA.js → chunk-XZJS2F5Q.js} +1 -2
- package/dist/config.d.ts +23 -5
- package/dist/config.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/nextjs/index.d.ts +9 -12
- package/dist/nextjs/index.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
**Fix: the `config.matcher` recommendation from 0.3.0 does not build under Next 16.** Next requires
|
|
6
|
+
`config.matcher` entries to be inline string literals it can statically parse — `export const config
|
|
7
|
+
= authProxyConfig` (an imported object) fails the build with "the exported `config` field needs to
|
|
8
|
+
be a static object". 0.3.0 shipped and documented exactly that broken pattern.
|
|
9
|
+
|
|
10
|
+
- **Removed `authProxyConfig`.** It could not be used for its only purpose. Apps paste the matcher
|
|
11
|
+
literal inline instead.
|
|
12
|
+
- **Added the `AuthMatcher` type** (`typeof AUTH_MATCHER`). Pin the inlined literal with
|
|
13
|
+
`satisfies { readonly matcher: readonly [AuthMatcher] }` so `tsc` fails if the copy ever drifts
|
|
14
|
+
from the shared `AUTH_MATCHER`. `AUTH_MATCHER` itself is unchanged.
|
|
15
|
+
- **Docs:** `proxy.ts` is now the one canonical filename (Next 16 deprecates `middleware.ts`,
|
|
16
|
+
including for apps that wrap the proxy — `safeAuthProxy` is a `proxy.ts` default export too).
|
|
17
|
+
|
|
3
18
|
## 0.3.0
|
|
4
19
|
|
|
5
20
|
**Thicken the package so apps need no local auth layer or copy-pasted wiring.** Everything the apps
|
package/IDENTITY.md
CHANGED
|
@@ -51,11 +51,14 @@ app → own /login /signup /verify /reset routes renderi
|
|
|
51
51
|
1. `pnpm add @spawn-llc/auth @spawn-llc/design-system`.
|
|
52
52
|
2. Env from the "Spawn" application: `WORKOS_API_KEY`, `WORKOS_CLIENT_ID`, `WORKOS_COOKIE_PASSWORD`,
|
|
53
53
|
`NEXT_PUBLIC_WORKOS_REDIRECT_URI=<app>/callback`; add that redirect URI in the WorkOS dashboard.
|
|
54
|
-
3. `proxy.ts`
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
3. `proxy.ts` (Next 16's name — `middleware.ts` is deprecated) → `export default authProxy()` plus a
|
|
55
|
+
`config` whose `matcher` is the inline hardened literal, pinned with
|
|
56
|
+
`satisfies { readonly matcher: readonly [AuthMatcher] }` (Next requires an inline literal, so it
|
|
57
|
+
can't be an imported object; the `AuthMatcher` type fails `tsc` if the copy drifts from
|
|
58
|
+
`AUTH_MATCHER`). `app/callback/route.ts` → `export const GET = handleCallback()`. If the app must
|
|
59
|
+
stay up on preview deploys with no secrets, make the `proxy.ts` default export
|
|
60
|
+
`safeAuthProxy({ onUnconfigured })`. Gate API handlers with `requireApiUser()` (401 JSON, never a
|
|
61
|
+
redirect); pages/layouts with `requireUser()`.
|
|
59
62
|
4. Build `/login`, `/signup`, `/verify`, `/reset` rendering the DS screens, wired to the headless
|
|
60
63
|
flows via `"use server"` actions.
|
|
61
64
|
5. Internal-only tool? Set `ALLOWED_EMAIL_DOMAINS=spawnpartners.com`.
|
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ internal tools), `AUTH_PUBLIC_PATHS`.
|
|
|
24
24
|
`createIdentityGateway()`, domain types (`Role`, `Session`, `Member`, `Invite`), and policy
|
|
25
25
|
(`isWorkosConfigured`, `isAllowedEmail`, `isPublicPath`).
|
|
26
26
|
- **`@spawn-llc/auth/config`** — the edge-safe policy alone (import from middleware; no SDK): the
|
|
27
|
-
gate + `isPublicPath` / `appPublicPaths` and the shared hardened matcher (`AUTH_MATCHER
|
|
28
|
-
`
|
|
27
|
+
gate + `isPublicPath` / `appPublicPaths` and the shared hardened matcher (`AUTH_MATCHER` + its
|
|
28
|
+
`AuthMatcher` literal type for pinning an app's inlined `config.matcher`).
|
|
29
29
|
- **`@spawn-llc/auth/nextjs`** — the Next server surface: the session seam
|
|
30
30
|
(`currentUser` / `requireUser` / `requireApiUser` / `session` / `signOut` / `switchOrganization`),
|
|
31
31
|
`authProxy()` / `safeAuthProxy()`, `handleCallback()`, and the headless flows
|
|
@@ -34,16 +34,36 @@ internal tools), `AUTH_PUBLIC_PATHS`.
|
|
|
34
34
|
|
|
35
35
|
## Wire an app (Next.js)
|
|
36
36
|
|
|
37
|
+
Next 16 calls this file `proxy.ts` (the old `middleware.ts` name is deprecated). Next requires
|
|
38
|
+
`config.matcher` to be an **inline** string literal — a `const`/imported object fails the build — so
|
|
39
|
+
paste the matcher and pin it to the shared `AuthMatcher` type, which fails `tsc` if it ever drifts:
|
|
40
|
+
|
|
37
41
|
```ts
|
|
38
42
|
// proxy.ts (refresh the session on every route; gate in-app via requireUser())
|
|
39
43
|
import { authProxy } from "@spawn-llc/auth/nextjs";
|
|
40
|
-
import {
|
|
44
|
+
import type { AuthMatcher } from "@spawn-llc/auth/config";
|
|
41
45
|
export default authProxy();
|
|
42
|
-
export const config =
|
|
46
|
+
export const config = {
|
|
47
|
+
matcher: ["/((?!_next/static|_next/image|favicon\\.ico|(?!(?:.*/)?api/).*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)"],
|
|
48
|
+
} satisfies { readonly matcher: readonly [AuthMatcher] };
|
|
43
49
|
```
|
|
44
50
|
|
|
45
|
-
Need to fail closed when the WORKOS_* secrets are absent (a preview deploy)?
|
|
46
|
-
|
|
51
|
+
Need to fail closed when the WORKOS_* secrets are absent (a preview deploy)? Keep the same `proxy.ts`
|
|
52
|
+
but wrap with `safeAuthProxy({ onUnconfigured })` as the default export:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// proxy.ts (fail closed when identity is unconfigured)
|
|
56
|
+
import { safeAuthProxy } from "@spawn-llc/auth/nextjs";
|
|
57
|
+
import { isPublicPath } from "@spawn-llc/auth/config";
|
|
58
|
+
import { NextResponse } from "next/server";
|
|
59
|
+
export default safeAuthProxy({
|
|
60
|
+
onUnconfigured: (req) =>
|
|
61
|
+
isPublicPath(req.nextUrl.pathname)
|
|
62
|
+
? NextResponse.next()
|
|
63
|
+
: NextResponse.redirect(new URL("/login", req.url)),
|
|
64
|
+
});
|
|
65
|
+
// (same `config` block as above)
|
|
66
|
+
```
|
|
47
67
|
|
|
48
68
|
```ts
|
|
49
69
|
// app/(any)/api/route.ts — gate a route handler (401 JSON, never a redirect)
|
|
@@ -11,7 +11,6 @@ function appPublicPaths(extra) {
|
|
|
11
11
|
return [...publicPaths(), ...extra];
|
|
12
12
|
}
|
|
13
13
|
var AUTH_MATCHER = "/((?!_next/static|_next/image|favicon\\.ico|(?!(?:.*/)?api/).*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)";
|
|
14
|
-
var authProxyConfig = { matcher: [AUTH_MATCHER] };
|
|
15
14
|
function isWorkosConfigured() {
|
|
16
15
|
return Boolean(
|
|
17
16
|
process.env.WORKOS_API_KEY && (process.env.WORKOS_CLIENT_ID || process.env.NEXT_PUBLIC_WORKOS_CLIENT_ID) && process.env.WORKOS_COOKIE_PASSWORD
|
|
@@ -47,4 +46,4 @@ function workosClientId() {
|
|
|
47
46
|
return id;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
export { AUTH_MATCHER, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths, assertEmailDomainGate,
|
|
49
|
+
export { AUTH_MATCHER, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths, assertEmailDomainGate, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, requiresEmailDomainGate, workosClientId };
|
package/dist/config.d.ts
CHANGED
|
@@ -29,10 +29,28 @@ declare function appPublicPaths(extra: string[]): string[];
|
|
|
29
29
|
* handler — the matcher refreshes the cookie, it does not protect.
|
|
30
30
|
*/
|
|
31
31
|
declare const AUTH_MATCHER = "/((?!_next/static|_next/image|favicon\\.ico|(?!(?:.*/)?api/).*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)";
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The literal type of {@link AUTH_MATCHER}, for pinning an app's inlined matcher to the shared
|
|
34
|
+
* source without any runtime import.
|
|
35
|
+
*
|
|
36
|
+
* Next requires `config.matcher` entries to be INLINE string literals it can statically parse at
|
|
37
|
+
* build time — a `const`/imported reference (`export const config = someObject`) fails the build
|
|
38
|
+
* with "the exported `config` field needs to be a static object". So an app cannot consume a
|
|
39
|
+
* ready-made config object; it must paste the literal. To stop that copy from silently drifting,
|
|
40
|
+
* pin it with `satisfies`:
|
|
41
|
+
*
|
|
42
|
+
* import { authProxy } from "@spawn-llc/auth/nextjs";
|
|
43
|
+
* import type { AuthMatcher } from "@spawn-llc/auth/config";
|
|
44
|
+
* export default authProxy();
|
|
45
|
+
* export const config = {
|
|
46
|
+
* matcher: [AUTH_MATCHER_LITERAL], // paste the value of AUTH_MATCHER inline (see README)
|
|
47
|
+
* } satisfies { readonly matcher: readonly [AuthMatcher] };
|
|
48
|
+
*
|
|
49
|
+
* `tsc` then fails if the pasted literal ever diverges from `AUTH_MATCHER`. The import is type-only,
|
|
50
|
+
* so nothing lands in the bundle. (The literal is spelled out in README.md / IDENTITY.md — it is
|
|
51
|
+
* omitted from this doc-comment because the regex contains a comment-terminating sequence.)
|
|
52
|
+
*/
|
|
53
|
+
type AuthMatcher = typeof AUTH_MATCHER;
|
|
36
54
|
/**
|
|
37
55
|
* WorkOS (the engine) is "configured" once its three required secrets are present. When it is NOT
|
|
38
56
|
* configured, the seam fails CLOSED (no session → redirect to sign-in) rather than letting requests
|
|
@@ -64,4 +82,4 @@ declare function isAllowedEmail(email: string | null | undefined): boolean;
|
|
|
64
82
|
/** The WorkOS client id, from either the server or the public var. */
|
|
65
83
|
declare function workosClientId(): string;
|
|
66
84
|
|
|
67
|
-
export { AUTH_MATCHER, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths, assertEmailDomainGate,
|
|
85
|
+
export { AUTH_MATCHER, type AuthMatcher, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths, assertEmailDomainGate, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, requiresEmailDomainGate, workosClientId };
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { AUTH_MATCHER, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths, assertEmailDomainGate,
|
|
1
|
+
export { AUTH_MATCHER, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths, assertEmailDomainGate, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, requiresEmailDomainGate, workosClientId } from './chunk-XZJS2F5Q.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { R as Role } from './domain-CmEhGW2N.js';
|
|
2
2
|
export { I as Invite, M as Member, O as OrgMembership, S as Session, U as User } from './domain-CmEhGW2N.js';
|
|
3
|
-
export { AUTH_MATCHER, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths,
|
|
3
|
+
export { AUTH_MATCHER, AuthMatcher, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, workosClientId } from './config.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The identity/tenancy port. WorkOS owns organizations, memberships (+ roles) and invitations; this
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AUTH_MATCHER, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths,
|
|
1
|
+
export { AUTH_MATCHER, DEFAULT_PUBLIC_PATHS, allowedEmailDomains, appPublicPaths, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, workosClientId } from './chunk-XZJS2F5Q.js';
|
|
2
2
|
|
|
3
3
|
// src/identity/workos-gateway.ts
|
|
4
4
|
var ROLES = ["owner", "admin", "member"];
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as next_server from 'next/server';
|
|
2
2
|
import { NextResponse, NextMiddleware, NextRequest, NextFetchEvent } from 'next/server';
|
|
3
3
|
import { U as User, S as Session } from '../domain-CmEhGW2N.js';
|
|
4
|
-
export { AUTH_MATCHER,
|
|
4
|
+
export { AUTH_MATCHER, AuthMatcher } from '../config.js';
|
|
5
5
|
|
|
6
6
|
/** The signed-in Spawn user, or null. Applies the optional email-domain gate. */
|
|
7
7
|
declare function currentUser(): Promise<User | null>;
|
|
@@ -37,16 +37,13 @@ declare function userDisplayName(user: User): string;
|
|
|
37
37
|
* The app middleware/proxy. Because Spawn renders its OWN auth screens (headless), this only
|
|
38
38
|
* **refreshes** the WorkOS session cookie on each request — it does NOT redirect to any hosted page.
|
|
39
39
|
* Route protection is done in-app: authed layouts call `requireUser()`, which redirects
|
|
40
|
-
* unauthenticated visitors to `/login` (a Spawn screen). Use as the app's `proxy.ts` default export
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* import { authProxyConfig } from "@spawn-llc/auth/config";
|
|
45
|
-
* export default authProxy();
|
|
46
|
-
* export const config = authProxyConfig;
|
|
40
|
+
* unauthenticated visitors to `/login` (a Spawn screen). Use as the app's `proxy.ts` default export
|
|
41
|
+
* (Next 16's name; `middleware.ts` is deprecated). Next requires `config.matcher` to be an inline
|
|
42
|
+
* string literal, so paste the matcher and pin it to the shared `AuthMatcher` type — see
|
|
43
|
+
* {@link AuthMatcher} in `../config` for the exact snippet.
|
|
47
44
|
*
|
|
48
45
|
* When an app needs its own fail-closed behavior for the WORKOS-unconfigured case (a preview deploy
|
|
49
|
-
* with no secrets), wrap with `safeAuthProxy`
|
|
46
|
+
* with no secrets), wrap with `safeAuthProxy` — still from `proxy.ts`.
|
|
50
47
|
*
|
|
51
48
|
* Pass `{ enforce: true }` only if you want middleware-level gating to WorkOS's hosted UI instead
|
|
52
49
|
* of your own screens (not the Spawn default).
|
|
@@ -77,9 +74,9 @@ declare function authProxy(options?: {
|
|
|
77
74
|
* app-supplied `onUnconfigured` handler so the app keeps its own fallback (redirect to `/login`,
|
|
78
75
|
* 404 the gated area, `NextResponse.next()` for a public-only preview, …).
|
|
79
76
|
*
|
|
80
|
-
* //
|
|
77
|
+
* // proxy.ts
|
|
81
78
|
* import { safeAuthProxy } from "@spawn-llc/auth/nextjs";
|
|
82
|
-
* import {
|
|
79
|
+
* import { isPublicPath } from "@spawn-llc/auth/config";
|
|
83
80
|
* import { NextResponse } from "next/server";
|
|
84
81
|
* export default safeAuthProxy({
|
|
85
82
|
* onUnconfigured: (req) =>
|
|
@@ -87,7 +84,7 @@ declare function authProxy(options?: {
|
|
|
87
84
|
* ? NextResponse.next()
|
|
88
85
|
* : NextResponse.redirect(new URL("/login", req.url)),
|
|
89
86
|
* });
|
|
90
|
-
* export const config =
|
|
87
|
+
* // export const config = { matcher: [...] } satisfies { readonly matcher: readonly [AuthMatcher] }
|
|
91
88
|
*
|
|
92
89
|
* The configured/unconfigured decision is made ONCE, when the middleware is constructed (module
|
|
93
90
|
* load) — identity config does not change between requests within a running process.
|
package/dist/nextjs/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isWorkosConfigured, isAllowedEmail, publicPaths, workosClientId } from '../chunk-
|
|
2
|
-
export { AUTH_MATCHER
|
|
1
|
+
import { isWorkosConfigured, isAllowedEmail, publicPaths, workosClientId } from '../chunk-XZJS2F5Q.js';
|
|
2
|
+
export { AUTH_MATCHER } from '../chunk-XZJS2F5Q.js';
|
|
3
3
|
import { headers, cookies } from 'next/headers';
|
|
4
4
|
import { redirect } from 'next/navigation';
|
|
5
5
|
import { NextResponse } from 'next/server';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spawn-llc/auth",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Spawn shared identity — headless auth for the whole Spawn suite. One WorkOS project (named \"Spawn\") backs every app; this is the only place that touches it. WorkOS is an invisible engine.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|