@~lyre/auth 0.11.0 → 0.12.0
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 +93 -6
- package/dist/chunk-F2GDXTY4.js +971 -0
- package/dist/index.d.ts +323 -1
- package/dist/index.js +37 -1
- package/dist/nest.d.ts +22 -4
- package/dist/nest.js +53 -7
- package/dist/sveltekit.d.ts +19 -1
- package/dist/sveltekit.js +141 -36
- package/package.json +6 -4
- package/dist/chunk-TQNQLX3C.js +0 -338
package/README.md
CHANGED
|
@@ -13,6 +13,89 @@ Ships built ESM output from `dist/`, with type declarations.
|
|
|
13
13
|
pnpm add @~lyre/auth # or: npm i / yarn add
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
## Sign in with Nipate Accounts
|
|
17
|
+
|
|
18
|
+
Since 0.12.0 sign-in follows the Accounts OpenID Connect contract
|
|
19
|
+
(`nipate/platform/docs/12-accounts-oidc.md`): authorization code + PKCE S256
|
|
20
|
+
for every app, `state` + `nonce`, form-encoded token requests with client
|
|
21
|
+
authentication, an RS256 `id_token` verified against the JWKS, and rotating
|
|
22
|
+
refresh tokens. No new dependencies: JWT checks use `node:crypto`.
|
|
23
|
+
|
|
24
|
+
Register the app in Accounts first (client id = app id; exact redirect URIs).
|
|
25
|
+
Confidential apps (anything with a server) get a client secret; public apps
|
|
26
|
+
(browser-only, CLI) have none and authenticate with `client_id` only.
|
|
27
|
+
|
|
28
|
+
### SvelteKit
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// src/hooks.server.ts
|
|
32
|
+
import { createAccountsClientConfig } from '@~lyre/auth';
|
|
33
|
+
import { createAuthHandle } from '@~lyre/auth/sveltekit';
|
|
34
|
+
|
|
35
|
+
export const handle = createAuthHandle({
|
|
36
|
+
config: createAccountsClientConfig({
|
|
37
|
+
baseUrl: 'https://accounts.nipate.africa', // ACCOUNTS_BASE_URL (issuer: ACCOUNTS_ISSUER)
|
|
38
|
+
clientId: 'shop', // ACCOUNTS_CLIENT_ID
|
|
39
|
+
clientSecret: process.env.ACCOUNTS_CLIENT_SECRET, // confidential apps only
|
|
40
|
+
redirectUri: 'https://shop.example/auth/callback',
|
|
41
|
+
logoutRedirectUri: 'https://shop.example/'
|
|
42
|
+
// scopes default to: openid profile email offline_access
|
|
43
|
+
}),
|
|
44
|
+
sessionSecret: process.env.SESSION_SECRET, // signs the session AND the login-state cookie
|
|
45
|
+
endAccountsSession: false, // true: logout also ends the Accounts session
|
|
46
|
+
protect: (event) => event.url.pathname.startsWith('/account')
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`/auth/login` stores `state`, `nonce` and the PKCE verifier in a 10-minute
|
|
51
|
+
signed httpOnly cookie and redirects to `/auth/authorize`; `/auth/callback`
|
|
52
|
+
checks state, exchanges the code, verifies the id_token and writes the app's
|
|
53
|
+
own session; requests refresh the access token shortly before it expires
|
|
54
|
+
(storing the rotated refresh token; a refused refresh signs the user out);
|
|
55
|
+
`/auth/logout` revokes the refresh token. Failures become a 400 naming the
|
|
56
|
+
`OidcError` code, or whatever `onAuthError` returns. Mock mode
|
|
57
|
+
(`useMock`) keeps the old demo flow for local work without Accounts.
|
|
58
|
+
|
|
59
|
+
### Plain Node
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { createOidcClient, isOidcError } from '@~lyre/auth';
|
|
63
|
+
|
|
64
|
+
const accounts = createOidcClient({
|
|
65
|
+
issuer: 'https://accounts.nipate.africa',
|
|
66
|
+
clientId: 'premail',
|
|
67
|
+
clientSecret: process.env.ACCOUNTS_CLIENT_SECRET, // omit for a public client
|
|
68
|
+
redirectUri: 'https://premail.example/auth/callback',
|
|
69
|
+
postLogoutRedirectUri: 'https://premail.example/',
|
|
70
|
+
discovery: true // optional: read endpoints from /.well-known/openid-configuration (cached)
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 1. Start: keep state, nonce and codeVerifier (server-side or in a signed httpOnly cookie).
|
|
74
|
+
const login = await accounts.beginLogin();
|
|
75
|
+
// createLoginStateCookie(login, { nextPath, secret }) / readLoginStateCookie(header, { secret })
|
|
76
|
+
res.redirect(login.url);
|
|
77
|
+
|
|
78
|
+
// 2. Callback: throws OidcError with a code (state_mismatch, nonce_mismatch, audience_mismatch,
|
|
79
|
+
// token_expired, token_request_failed + oauthError, …).
|
|
80
|
+
const { claims, tokens } = await accounts.handleCallback({ url: req.url, expected: login });
|
|
81
|
+
// claims.sub is the user id. Create your own session; store tokens.refreshToken.
|
|
82
|
+
|
|
83
|
+
// 3. Later
|
|
84
|
+
const fresh = await accounts.refresh(tokens.refreshToken!); // ALWAYS store fresh.refreshToken
|
|
85
|
+
const { claims: at } = await accounts.verifyAccessToken(fresh.accessToken); // local JWKS check
|
|
86
|
+
await accounts.revoke(fresh.refreshToken!);
|
|
87
|
+
res.redirect(await accounts.logoutUrl()); // optional: end the Accounts session
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Services that only receive bearer tokens use `createAccessTokenVerifier({ issuer, audience })`,
|
|
91
|
+
or `createUserSessionResolver({ config: { baseUrl }, audience })` from `@~lyre/auth/nest`.
|
|
92
|
+
Keys are cached and refetched when a token names an unknown `kid`; if the JWKS
|
|
93
|
+
is unreachable, userinfo vouches for the token instead (`remoteFallback`).
|
|
94
|
+
|
|
95
|
+
**Deprecated in 0.12.0** (still exported; removed in a later major):
|
|
96
|
+
`beginAccountsLoginRedirect`, `handleAccountsCallback`, `exchangeAuthorizationCode`,
|
|
97
|
+
`logoutFromAccounts`, and the `AuthorizationState` / `AccountsTokenResponse` types.
|
|
98
|
+
|
|
16
99
|
## Entry points
|
|
17
100
|
|
|
18
101
|
### `@~lyre/auth` — framework-agnostic core
|
|
@@ -33,7 +116,7 @@ import {
|
|
|
33
116
|
} from '@~lyre/auth';
|
|
34
117
|
```
|
|
35
118
|
|
|
36
|
-
The core depends only on `node:crypto` — no framework required.
|
|
119
|
+
The core depends only on `node:crypto` and `fetch` — no framework required.
|
|
37
120
|
|
|
38
121
|
### `@~lyre/auth/sveltekit` — turnkey SvelteKit adapter
|
|
39
122
|
|
|
@@ -65,7 +148,10 @@ import { createServiceKeyResolver, createUserSessionResolver, hasScopes }
|
|
|
65
148
|
from '@~lyre/auth/nest';
|
|
66
149
|
|
|
67
150
|
const keys = createServiceKeyResolver({ config: { baseUrl: ACCOUNTS_BASE_URL } });
|
|
68
|
-
const sessions = createUserSessionResolver({
|
|
151
|
+
const sessions = createUserSessionResolver({
|
|
152
|
+
config: { baseUrl: ACCOUNTS_BASE_URL },
|
|
153
|
+
audience: 'my-app' // accept only tokens issued to these client ids
|
|
154
|
+
});
|
|
69
155
|
|
|
70
156
|
// In a Nest guard (req is the Fastify request):
|
|
71
157
|
const key = await keys.resolve(req);
|
|
@@ -75,9 +161,9 @@ if (key.status === 'ok') {
|
|
|
75
161
|
// key.context.accessibleApps tells you which apps this key may act on
|
|
76
162
|
}
|
|
77
163
|
|
|
78
|
-
//
|
|
164
|
+
// An access-token bearer resolves to who + which app, verified locally against the JWKS:
|
|
79
165
|
const user = await sessions.resolve(req);
|
|
80
|
-
// → { userId, sessionId, appId, tenantId,
|
|
166
|
+
// → { userId, sessionId, appId, tenantId, scopes, verifiedBy } | null
|
|
81
167
|
```
|
|
82
168
|
|
|
83
169
|
Semantics match `createServiceKeyHandle`: **absent key → `absent`** (fall through to
|
|
@@ -87,8 +173,9 @@ results too, so a bad key can't hammer Accounts. Neither resolver throws: a
|
|
|
87
173
|
transport failure reads as "not authenticated", so a flaky Accounts cannot 500
|
|
88
174
|
the consuming service.
|
|
89
175
|
|
|
90
|
-
|
|
91
|
-
|
|
176
|
+
JWT bearers are verified locally (`aud` → `appId`, `tid` → `tenantId`, `sid` →
|
|
177
|
+
`sessionId`). Opaque pre-JWT session ids still go to the legacy
|
|
178
|
+
`GET /api/auth/me` until you pass `legacySessionTokens: false`.
|
|
92
179
|
|
|
93
180
|
## Publishing
|
|
94
181
|
|