@oauth42/next 0.4.8 → 0.5.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 +164 -2
- package/dist/build-manifest.json +59 -0
- package/dist/client/index.d.mts +15 -6
- package/dist/client/index.d.ts +15 -6
- package/dist/client/index.js +23 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +23 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/index-BuZTiUds.d.mts +144 -0
- package/dist/index-BuZTiUds.d.ts +144 -0
- package/dist/index.d.mts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +617 -104
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +613 -103
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/index.d.mts +3 -39
- package/dist/middleware/index.d.ts +3 -39
- package/dist/middleware/index.js +283 -34
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +284 -34
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/{auth-BThRQMzh.d.ts → par-Cyu6M1e6.d.mts} +36 -11
- package/dist/{auth-BThRQMzh.d.mts → par-DQNt8j-s.d.ts} +36 -11
- package/dist/server/index.d.mts +7 -3
- package/dist/server/index.d.ts +7 -3
- package/dist/server/index.js +657 -123
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +650 -122
- package/dist/server/index.mjs.map +1 -1
- package/package.json +14 -5
- package/src/types/index.ts +2 -0
- package/src/types/next-auth.d.ts +2 -0
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ export const { handlers, auth } = createAuth({
|
|
|
42
42
|
// Optional: Override defaults
|
|
43
43
|
scopes: ['openid', 'profile', 'email'],
|
|
44
44
|
pkceEnabled: true,
|
|
45
|
+
// Recommended: keep refresh tokens in the OAuth42 backend, not in cookies.
|
|
46
|
+
sessionStore: 'backend',
|
|
45
47
|
});
|
|
46
48
|
|
|
47
49
|
// App Router
|
|
@@ -230,9 +232,10 @@ export function MyClientComponent() {
|
|
|
230
232
|
|
|
231
233
|
```typescript
|
|
232
234
|
// middleware.ts
|
|
233
|
-
import { withOAuth42Auth } from '@oauth42/next/
|
|
235
|
+
import { withOAuth42Auth } from '@oauth42/next/middleware';
|
|
234
236
|
|
|
235
237
|
export default withOAuth42Auth({
|
|
238
|
+
sessionStore: 'backend',
|
|
236
239
|
pages: {
|
|
237
240
|
signIn: '/login',
|
|
238
241
|
},
|
|
@@ -272,6 +275,63 @@ export const config = {
|
|
|
272
275
|
|
|
273
276
|
**Note:** When using middleware, you don't need client-side auth checks in page components - the middleware handles authentication before the page renders.
|
|
274
277
|
|
|
278
|
+
### Backend-Owned Opaque Sessions
|
|
279
|
+
|
|
280
|
+
Use `sessionStore: 'backend'` for Gmail-style persistent sessions with immediate server-side revocation:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// lib/auth.ts
|
|
284
|
+
import { createAuth } from '@oauth42/next/server';
|
|
285
|
+
|
|
286
|
+
const { auth, handlers } = createAuth({
|
|
287
|
+
clientId: process.env.OAUTH42_CLIENT_ID,
|
|
288
|
+
clientSecret: process.env.OAUTH42_CLIENT_SECRET,
|
|
289
|
+
issuer: process.env.OAUTH42_ISSUER,
|
|
290
|
+
cookiePrefix: 'oauth42-app',
|
|
291
|
+
sessionStore: 'backend',
|
|
292
|
+
session: {
|
|
293
|
+
maxAge: 365 * 24 * 60 * 60,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
export const authOptions = auth;
|
|
298
|
+
export { handlers };
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// middleware.ts
|
|
303
|
+
import { withOAuth42Auth } from '@oauth42/next/middleware';
|
|
304
|
+
|
|
305
|
+
export default withOAuth42Auth({
|
|
306
|
+
cookiePrefix: 'oauth42-app',
|
|
307
|
+
sessionStore: 'backend',
|
|
308
|
+
clientId: process.env.OAUTH42_CLIENT_ID,
|
|
309
|
+
clientSecret: process.env.OAUTH42_CLIENT_SECRET,
|
|
310
|
+
issuer: process.env.OAUTH42_ISSUER,
|
|
311
|
+
session: {
|
|
312
|
+
maxAge: 365 * 24 * 60 * 60,
|
|
313
|
+
},
|
|
314
|
+
publicPaths: ['/api/auth', '/auth'],
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
In this mode, the browser cookie contains an opaque backend session id inside the NextAuth session wrapper. OAuth access and refresh tokens are stored and rotated by the Rust OAuth42 backend. The Next.js SDK calls backend session endpoints; it does not connect to Redis.
|
|
319
|
+
|
|
320
|
+
Since 0.5.1, backend-session resolution consumes the backend's live `user_info`
|
|
321
|
+
response in the same request. The backend rechecks current session, user, tenant,
|
|
322
|
+
application, token, and authentication-policy authority before returning it. The
|
|
323
|
+
SDK validates its subject and profile fields; it never treats cached session
|
|
324
|
+
metadata as live identity. Each new request still resolves the session, so
|
|
325
|
+
revocation and group changes remain effective without an authorization cache.
|
|
326
|
+
Older backends that omit `user_info` retain the separate live `/oauth2/userinfo`
|
|
327
|
+
request. A present but invalid profile fails closed.
|
|
328
|
+
|
|
329
|
+
Use the SDK from server components and authenticated server route handlers.
|
|
330
|
+
Initial page data belongs in server components; interactive browser requests use
|
|
331
|
+
same-origin Next.js routes. Keep issuer URLs, credentials, and backend transport
|
|
332
|
+
on the server. Portal and admin may continue using the repository's local `file:`
|
|
333
|
+
dependency; the same implementation is packaged for publication as `@oauth42/next@0.5.1`.
|
|
334
|
+
|
|
275
335
|
### Server-Side Session
|
|
276
336
|
|
|
277
337
|
```typescript
|
|
@@ -367,4 +427,106 @@ Wrapper for protected content.
|
|
|
367
427
|
Copyright (c) 2024 OAuth42, Inc. All rights reserved.
|
|
368
428
|
|
|
369
429
|
This SDK is proprietary software provided by OAuth42, Inc. for use with OAuth42 services.
|
|
370
|
-
See LICENSE file for terms and conditions.
|
|
430
|
+
See LICENSE file for terms and conditions.
|
|
431
|
+
|
|
432
|
+
### Security: session refresh and request forwarding
|
|
433
|
+
|
|
434
|
+
Refresh concurrency is isolated by issuer, client credentials, subject and refresh
|
|
435
|
+
grant. Middleware passes refreshed encrypted NextAuth cookies to the current
|
|
436
|
+
request; callers cannot override session access tokens with
|
|
437
|
+
`x-oauth42-refreshed-token`. Upgrade middleware and session helpers together.
|
|
438
|
+
`getOAuth42Session` returns `null` for failed/revoked sessions. Invalid refresh
|
|
439
|
+
grants and expired credentials fail closed, including concurrent rotation races;
|
|
440
|
+
the former stale-session grace period has been removed. Custom authorization
|
|
441
|
+
callbacks apply to backend sessions and refresh requests as well as normal requests.
|
|
442
|
+
|
|
443
|
+
Protected middleware and session callbacks verify current identity with the issuer's
|
|
444
|
+
`/oauth2/userinfo` endpoint and use current groups for authorization. Revocation,
|
|
445
|
+
subject mismatch, malformed profile claims or issuer failure rejects the session.
|
|
446
|
+
This adds a live request per check and requires issuer availability. Requests have
|
|
447
|
+
a 30-second timeout, reject redirects and disable caching. `NEXTAUTH_SECRET` must be
|
|
448
|
+
configured. Session callbacks remove user data and credentials on verification failure.
|
|
449
|
+
|
|
450
|
+
### Signed client authentication and DPoP
|
|
451
|
+
|
|
452
|
+
Confidential Node.js backends can use a retained P-256 key instead of a shared
|
|
453
|
+
client secret. Load keys from protected server storage:
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { createPrivateKey } from 'node:crypto';
|
|
457
|
+
import { readFileSync } from 'node:fs';
|
|
458
|
+
import { createAuth, OAuth42ClientAssertion, OAuth42DPoP } from '@oauth42/next/server';
|
|
459
|
+
|
|
460
|
+
const clientAssertionKey = new OAuth42ClientAssertion(
|
|
461
|
+
'signing-key-2026', createPrivateKey(readFileSync('/run/secrets/oauth-client-signing.pem')),
|
|
462
|
+
);
|
|
463
|
+
const dpop = new OAuth42DPoP(createPrivateKey(readFileSync('/run/secrets/oauth-dpop.pem')));
|
|
464
|
+
export const oauthOptions = {
|
|
465
|
+
clientId: 'registered-client', issuer: 'https://auth.example.com',
|
|
466
|
+
clientAssertionKey, dpop, sessionStore: 'jwt' as const,
|
|
467
|
+
};
|
|
468
|
+
export const { handlers, auth } = createAuth(oauthOptions);
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Register **only** `clientAssertionKey.publicJwk` in
|
|
472
|
+
`security_settings.jwks.keys` and select `token_endpoint_auth_method=private_key_jwt`.
|
|
473
|
+
Keep the key instances in server-only modules. The provider gives a private JWK
|
|
474
|
+
set to NextAuth/openid-client internally; never expose provider configuration or
|
|
475
|
+
`oidcJwks` to browsers, logs or registration. Software keys do not establish
|
|
476
|
+
hardware custody or make a native/public app confidential.
|
|
477
|
+
|
|
478
|
+
Pass the same `clientAssertionKey` and `dpop` options to `withOAuth42Auth` and,
|
|
479
|
+
when used, `createHostedAuthCallback`. The explicit refresh helper also accepts
|
|
480
|
+
`refreshAccessToken(token, clientId, '', issuer, dpop, clientAssertionKey)`.
|
|
481
|
+
Signing keys require Node.js. For Next.js 15.5+ middleware set
|
|
482
|
+
`export const config = { runtime: 'nodejs' }`, as described in the
|
|
483
|
+
[Next.js runtime documentation](https://nextjs.org/docs/15/app/api-reference/file-conventions/middleware#runtime).
|
|
484
|
+
Both `sessionStore: 'jwt'` and `sessionStore: 'backend'` support this configuration.
|
|
485
|
+
Backend handles require the retained proof key on create, resolve, tenant selection
|
|
486
|
+
and revocation. All replicas must use the same protected key material.
|
|
487
|
+
|
|
488
|
+
Assertions expire after 60 seconds and are regenerated on each nonce retry.
|
|
489
|
+
The NextAuth callback retains openid-client's ID-token signature, issuer, nonce,
|
|
490
|
+
state and PKCE checks. Shared secrets are omitted from signed requests and removed
|
|
491
|
+
from session data when asymmetric authentication is selected. Userinfo receives
|
|
492
|
+
the resource credential and its proof. HTTPS is required outside loopback; credential
|
|
493
|
+
redirects are refused. Rotate by registering replacement public keys with distinct
|
|
494
|
+
IDs, switching the retained signer, then removing the old public key.
|
|
495
|
+
|
|
496
|
+
Tests exercise the installed NextAuth client over real HTTP, including a nonce
|
|
497
|
+
challenge and rejection of a wrong ID-token nonce. The normal backend integration
|
|
498
|
+
suite also runs `scripts/ci/next-sdk-interop.sh` against O42/PostgreSQL/Redis for
|
|
499
|
+
signed code exchange, DPoP userinfo, refresh rotation and client-key rotation.
|
|
500
|
+
|
|
501
|
+
### Pushed authorization (PAR)
|
|
502
|
+
|
|
503
|
+
Set `pushedAuthorization: true` in `createAuth` and export the returned
|
|
504
|
+
`handlers.GET` and `handlers.POST` from the App Router authentication route. This
|
|
505
|
+
wraps the actual NextAuth handler after it creates its state, nonce and PKCE
|
|
506
|
+
cookies. Using only the returned `auth` configuration does not enable PAR. Custom
|
|
507
|
+
App Router handlers can use `withPushedAuthorization(handler, options)`.
|
|
508
|
+
|
|
509
|
+
The push uses the configured client assertion and DPoP keys, preserves NextAuth's
|
|
510
|
+
cookies, and returns only `client_id` and the opaque `request_uri` to the browser.
|
|
511
|
+
A failed push fails sign-in; it never falls back to a full front-channel request.
|
|
512
|
+
Enable `require_pushed_authorization_requests` on the registered client after
|
|
513
|
+
rolling out compatible SDKs. Backend API access belongs in server routes/loaders;
|
|
514
|
+
never serialize these keys, backend handles or access tokens to client components.
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
## Publishing (maintainers)
|
|
518
|
+
|
|
519
|
+
Commit the SDK version and lockfile changes through a reviewed PR. Dispatch
|
|
520
|
+
`publish-sdk-next.yml` from `main` with `version_type=current`. The O42 runner
|
|
521
|
+
runs tests, type checking, build and packaging in Amazon Linux. A separate,
|
|
522
|
+
ten-minute GitHub-hosted job verifies the source and tarball checksum and
|
|
523
|
+
publishes that exact package with npm OIDC; it does not run package scripts.
|
|
524
|
+
No `NPM_TOKEN` secret is used. The workflow confirms the registry tarball matches
|
|
525
|
+
before creating `sdk-next-v<version>` in GitHub. A retry accepts an existing npm
|
|
526
|
+
version only when its bytes match the tested artifact.
|
|
527
|
+
|
|
528
|
+
The npm trusted publisher for `@oauth42/next` must specify organization `oauth42`,
|
|
529
|
+
repository `oauth42`, workflow `publish-sdk-next.yml`, and no environment.
|
|
530
|
+
Enable direct `npm publish`; separate dist-tag management is unnecessary.
|
|
531
|
+
Portal and admin can continue using their local SDK dependency while consumers
|
|
532
|
+
install the published version from npm.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sources": {
|
|
3
|
+
"src/client/auth.ts": "ce530d47640b70ecab27c45a0541a96165ec1c21df627601b959e56121620654",
|
|
4
|
+
"src/client/components.tsx": "8c93cf7875f227f772ddf8750f69222a775bd22f86d124cf50267f4f0df87f02",
|
|
5
|
+
"src/client/hooks.ts": "11d348e3aff8c29c51a31f124c69458c79c10d00159cd05680d927224c1bfda3",
|
|
6
|
+
"src/client/index.ts": "539b6e0cc12649a39753fc34208fcf2d31a0c51481d62d80e41986fe1443688f",
|
|
7
|
+
"src/index.ts": "62117c2656024fea9fd1a635ee6b5413c9082dd3a723a912ab002fdee5ab37e4",
|
|
8
|
+
"src/middleware/index.ts": "df24e14d480d5f2a4fc4ba149a83322d91f9c4ce75532a574ed82fb13b36bfd7",
|
|
9
|
+
"src/provider.ts": "c310939742675baed98a08b5bae2f326b5ec1844a958079f2e77d4716771a89e",
|
|
10
|
+
"src/server/auth.ts": "93362c571953498deefbf8f10595062e46c0023c043d617140e21c334d7b00d4",
|
|
11
|
+
"src/server/backend-session.ts": "e8cc3578fb3168209d8ab1f9fc9b273284100fb0864819b5d25e08b896c47fb4",
|
|
12
|
+
"src/server/client-assertion.ts": "3947dd97ca0830fa99233c0c8afe75517fcaea61a26ea122d45ac5061244a906",
|
|
13
|
+
"src/server/dpop.ts": "8e28084b5415f48cc7ea130a3befffdb5547d5520a8d03b4f92be35e62aa9930",
|
|
14
|
+
"src/server/hosted-auth-callback.ts": "3ab3c9659f94fe4110a542745660750802ada9465eb448b18f6927cac61ecef3",
|
|
15
|
+
"src/server/index.ts": "9496fbbbe541e969056e36ba434358d5005fbab928e9a1743d18c7ed79c43a88",
|
|
16
|
+
"src/server/middleware.ts": "dc5d323135fabf7a1353e8c1b29f44dd8df9a3685f06a9c2d18e0a104ef48cd3",
|
|
17
|
+
"src/server/par.ts": "de1a8126d2331e367437ca8f294b76ae638f619e927d1b8c5b2119b4e2c2644c",
|
|
18
|
+
"src/server/session.ts": "d10c6bce25fc37f2d0d1e991fa6e54de83479c2fb29345ebaf97ac142cf5b374",
|
|
19
|
+
"src/server/verified-userinfo.ts": "95885d3eda83aa2e208122837dfe1f54d00621059c7cb45a06ad80b43dea76dd",
|
|
20
|
+
"src/types/index.ts": "1842a9520cabc876b6bfb2224835a5d5e5c522c81380feecd3149e4be06f65f0",
|
|
21
|
+
"src/types/next-auth.d.ts": "1bc28fa97ed08663590865432926988c6a8d507db3eec80a95fb4a19f3f53eec",
|
|
22
|
+
"src/utils/hosted-auth.ts": "d2909d3bbcab299c0e58c25891a024a1ac5b3df696be688805088e6b63921754",
|
|
23
|
+
"scripts/build-manifest.mjs": "02c050c547eb67dd60347a61e1554b01a4f532fb7cb85dc9ec88392dce08ecaf",
|
|
24
|
+
"package.json": "bc878cc8cadfd201d83864a1fe6eb796bfdc3eef888a3582261c0376a983308f",
|
|
25
|
+
"package-lock.json": "ea37d84ae8a9bc3cf7edefdc5cb4c5751ffa181f60ba54b455e22d15ed0e01d7",
|
|
26
|
+
"tsconfig.json": "c8f87d61d0bb7e6835ee5eaec2f40b6415b009b5a5f7362702f0735925d4b496",
|
|
27
|
+
"tsup.config.ts": "29029ce1621d53ae94666c69e3adcbea8805917fabf0080b4f937783f70eaa0c"
|
|
28
|
+
},
|
|
29
|
+
"artifacts": {
|
|
30
|
+
"dist/client/index.d.mts": "56a41ee4bd5d9556754b14e0a894c0ac7c872191baad24399e7c53a94858dbf0",
|
|
31
|
+
"dist/client/index.d.ts": "56a41ee4bd5d9556754b14e0a894c0ac7c872191baad24399e7c53a94858dbf0",
|
|
32
|
+
"dist/client/index.js": "be19a91996fc2e84c514945010650664fd7e5765cc56bd27e3a0de3d3f199751",
|
|
33
|
+
"dist/client/index.js.map": "64190f8bfdf56021c96db4ebe67627f57f6938261dbcbaf2651d2cb577e64988",
|
|
34
|
+
"dist/client/index.mjs": "0f66584f2ad933e1edf47a6637634053d7113e13f492b862f268d48145a5b103",
|
|
35
|
+
"dist/client/index.mjs.map": "811b32edfbda5d24a8d88cbb97eaa6a35f4a38315cd5328d1d3128d59883d809",
|
|
36
|
+
"dist/index-BuZTiUds.d.mts": "4e2e6253ce53f2d2883d69b2ed87493e2934e2e38343a3174d00cda775f0138d",
|
|
37
|
+
"dist/index-BuZTiUds.d.ts": "4e2e6253ce53f2d2883d69b2ed87493e2934e2e38343a3174d00cda775f0138d",
|
|
38
|
+
"dist/index.d.mts": "cf9ed0a63dad62c0a5d37bdee7e12d79975903c5e0f80c728618a3e3f1256593",
|
|
39
|
+
"dist/index.d.ts": "baad366a72f148019ff6e6708622d8eebf7010572e43e35739c45df313454d48",
|
|
40
|
+
"dist/index.js": "e288a482b736f25299c19fdc05ba6b5520dacc5f02b399d5bb74fa578d0e8147",
|
|
41
|
+
"dist/index.js.map": "4eff08fd0633461c4c9c8d72f44a3b9980562c0547cfb8edf9eb18e1dd215d04",
|
|
42
|
+
"dist/index.mjs": "188169af59e5767fe805cdca32045b32df11408e217256ee94c327fb1bda64df",
|
|
43
|
+
"dist/index.mjs.map": "651390e78075aed9b0ce22b4784e2330fb60e53754ab70c7d9417ae25a3a538a",
|
|
44
|
+
"dist/middleware/index.d.mts": "503c360c09d3a4f1260989fb1b3018a0d6e098541549ad2a3392423689bdf12a",
|
|
45
|
+
"dist/middleware/index.d.ts": "6d22161ea4be84c8eebc0b1f5e33f51861521b2ce0ef8763f3ec281a492e593e",
|
|
46
|
+
"dist/middleware/index.js": "9cd651f93f570b0c05063279bd3c9d85640f61416a6ee395e4ed950d858375ea",
|
|
47
|
+
"dist/middleware/index.js.map": "4ac58e2ce92fa417c47a7228ed15d866539e7c5b3090fa56109ab76a6c3e55dc",
|
|
48
|
+
"dist/middleware/index.mjs": "2ad52231041fcc6297c31c299070a79ff91a2c20fb03703ea91160ad513005f5",
|
|
49
|
+
"dist/middleware/index.mjs.map": "8c707f7c5727555c5cd188a339f7f2fbea03772b3a04103881d47fa12c08d87a",
|
|
50
|
+
"dist/par-Cyu6M1e6.d.mts": "fb185b4eb0bb68d6080dce3c8bbaefebf68ca77cc4879b3c1a91790306c3067b",
|
|
51
|
+
"dist/par-DQNt8j-s.d.ts": "a79425198e956dc88c4ddb074fb6a0643ff2cd5a8193e5dcdb57c24360027d5d",
|
|
52
|
+
"dist/server/index.d.mts": "8d6c9bc181db1036d00f8364af212efecd96f1f33f7e44954d325ad937b4bf9e",
|
|
53
|
+
"dist/server/index.d.ts": "170094378d968949f8f446bf266cb7f1fbafce8c7b6ed294d25a1f56e71093f5",
|
|
54
|
+
"dist/server/index.js": "112424a7cdba7ef5aad935eb4d38a82c268acedcabd42ca67545a6d5e570717a",
|
|
55
|
+
"dist/server/index.js.map": "449f085b59d7473a7953bad5cbc48e613af2a45bfb63fdf377d55024d7f6a2e0",
|
|
56
|
+
"dist/server/index.mjs": "f28d09f8df7d4ea353ffe3b5990a48a7c26a2f989eebaeb4460849a79a1e6b5c",
|
|
57
|
+
"dist/server/index.mjs.map": "25baddd50877b56555d560b961ccebe560f51222f0202256faad34cc65c79461"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/client/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ export { Session } from 'next-auth';
|
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
|
|
6
|
-
type OAuth42Session<E =
|
|
6
|
+
type OAuth42Session<E extends object = Record<string, unknown>> = ({
|
|
7
7
|
user?: {
|
|
8
8
|
email?: string | null;
|
|
9
9
|
name?: string | null;
|
|
@@ -15,7 +15,7 @@ type OAuth42Session<E = {}> = ({
|
|
|
15
15
|
idToken?: string;
|
|
16
16
|
expires?: string;
|
|
17
17
|
}) & E;
|
|
18
|
-
interface UseOAuth42SessionReturn<E =
|
|
18
|
+
interface UseOAuth42SessionReturn<E extends object = Record<string, unknown>> {
|
|
19
19
|
session: OAuth42Session<E> | null;
|
|
20
20
|
loading: boolean;
|
|
21
21
|
error: Error | null;
|
|
@@ -26,11 +26,11 @@ interface UseOAuth42SessionReturn<E = {}> {
|
|
|
26
26
|
/**
|
|
27
27
|
* Hook to manage OAuth42 session with optional extra fields
|
|
28
28
|
*/
|
|
29
|
-
declare function useOAuth42Session<E =
|
|
29
|
+
declare function useOAuth42Session<E extends object = Record<string, unknown>>(): UseOAuth42SessionReturn<E>;
|
|
30
30
|
/**
|
|
31
31
|
* Hook to get the current OAuth42 user
|
|
32
32
|
*/
|
|
33
|
-
declare function useOAuth42User<E =
|
|
33
|
+
declare function useOAuth42User<E extends object = Record<string, unknown>>(): {
|
|
34
34
|
user: {
|
|
35
35
|
email?: string | null;
|
|
36
36
|
name?: string | null;
|
|
@@ -43,7 +43,7 @@ declare function useOAuth42User<E = {}>(): {
|
|
|
43
43
|
/**
|
|
44
44
|
* Hook to manage OAuth42 tokens
|
|
45
45
|
*/
|
|
46
|
-
declare function useOAuth42Tokens<E =
|
|
46
|
+
declare function useOAuth42Tokens<E extends object = Record<string, unknown>>(): {
|
|
47
47
|
accessToken: string | undefined;
|
|
48
48
|
idToken: string | undefined;
|
|
49
49
|
isExpired: boolean;
|
|
@@ -142,7 +142,7 @@ interface HostedAuthOptions {
|
|
|
142
142
|
* }
|
|
143
143
|
* ```
|
|
144
144
|
*/
|
|
145
|
-
declare function redirectToHostedAuth(options: HostedAuthOptions): void
|
|
145
|
+
declare function redirectToHostedAuth(options: HostedAuthOptions): Promise<void>;
|
|
146
146
|
/**
|
|
147
147
|
* Verify state parameter on OAuth2 callback
|
|
148
148
|
* Call this in your callback page to verify the state matches
|
|
@@ -218,6 +218,15 @@ interface AuthError {
|
|
|
218
218
|
requires_enrollment?: boolean;
|
|
219
219
|
enrollment_token?: string;
|
|
220
220
|
requires_mfa?: boolean;
|
|
221
|
+
mfa_methods?: string[];
|
|
222
|
+
email_otp_sent?: boolean;
|
|
223
|
+
email_otp_email?: string;
|
|
224
|
+
email_otp_rate_limited?: boolean;
|
|
225
|
+
phone_mfa_sent?: boolean;
|
|
226
|
+
phone_mfa_number?: string;
|
|
227
|
+
phone_mfa_method?: 'sms' | 'voice';
|
|
228
|
+
phone_mfa_rate_limited?: boolean;
|
|
229
|
+
phone_mfa_unavailable?: boolean;
|
|
221
230
|
}
|
|
222
231
|
/**
|
|
223
232
|
* Authenticate with email/password and complete OAuth PKCE flow
|
package/dist/client/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { Session } from 'next-auth';
|
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
|
|
6
|
-
type OAuth42Session<E =
|
|
6
|
+
type OAuth42Session<E extends object = Record<string, unknown>> = ({
|
|
7
7
|
user?: {
|
|
8
8
|
email?: string | null;
|
|
9
9
|
name?: string | null;
|
|
@@ -15,7 +15,7 @@ type OAuth42Session<E = {}> = ({
|
|
|
15
15
|
idToken?: string;
|
|
16
16
|
expires?: string;
|
|
17
17
|
}) & E;
|
|
18
|
-
interface UseOAuth42SessionReturn<E =
|
|
18
|
+
interface UseOAuth42SessionReturn<E extends object = Record<string, unknown>> {
|
|
19
19
|
session: OAuth42Session<E> | null;
|
|
20
20
|
loading: boolean;
|
|
21
21
|
error: Error | null;
|
|
@@ -26,11 +26,11 @@ interface UseOAuth42SessionReturn<E = {}> {
|
|
|
26
26
|
/**
|
|
27
27
|
* Hook to manage OAuth42 session with optional extra fields
|
|
28
28
|
*/
|
|
29
|
-
declare function useOAuth42Session<E =
|
|
29
|
+
declare function useOAuth42Session<E extends object = Record<string, unknown>>(): UseOAuth42SessionReturn<E>;
|
|
30
30
|
/**
|
|
31
31
|
* Hook to get the current OAuth42 user
|
|
32
32
|
*/
|
|
33
|
-
declare function useOAuth42User<E =
|
|
33
|
+
declare function useOAuth42User<E extends object = Record<string, unknown>>(): {
|
|
34
34
|
user: {
|
|
35
35
|
email?: string | null;
|
|
36
36
|
name?: string | null;
|
|
@@ -43,7 +43,7 @@ declare function useOAuth42User<E = {}>(): {
|
|
|
43
43
|
/**
|
|
44
44
|
* Hook to manage OAuth42 tokens
|
|
45
45
|
*/
|
|
46
|
-
declare function useOAuth42Tokens<E =
|
|
46
|
+
declare function useOAuth42Tokens<E extends object = Record<string, unknown>>(): {
|
|
47
47
|
accessToken: string | undefined;
|
|
48
48
|
idToken: string | undefined;
|
|
49
49
|
isExpired: boolean;
|
|
@@ -142,7 +142,7 @@ interface HostedAuthOptions {
|
|
|
142
142
|
* }
|
|
143
143
|
* ```
|
|
144
144
|
*/
|
|
145
|
-
declare function redirectToHostedAuth(options: HostedAuthOptions): void
|
|
145
|
+
declare function redirectToHostedAuth(options: HostedAuthOptions): Promise<void>;
|
|
146
146
|
/**
|
|
147
147
|
* Verify state parameter on OAuth2 callback
|
|
148
148
|
* Call this in your callback page to verify the state matches
|
|
@@ -218,6 +218,15 @@ interface AuthError {
|
|
|
218
218
|
requires_enrollment?: boolean;
|
|
219
219
|
enrollment_token?: string;
|
|
220
220
|
requires_mfa?: boolean;
|
|
221
|
+
mfa_methods?: string[];
|
|
222
|
+
email_otp_sent?: boolean;
|
|
223
|
+
email_otp_email?: string;
|
|
224
|
+
email_otp_rate_limited?: boolean;
|
|
225
|
+
phone_mfa_sent?: boolean;
|
|
226
|
+
phone_mfa_number?: string;
|
|
227
|
+
phone_mfa_method?: 'sms' | 'voice';
|
|
228
|
+
phone_mfa_rate_limited?: boolean;
|
|
229
|
+
phone_mfa_unavailable?: boolean;
|
|
221
230
|
}
|
|
222
231
|
/**
|
|
223
232
|
* Authenticate with email/password and complete OAuth PKCE flow
|
package/dist/client/index.js
CHANGED
|
@@ -241,7 +241,7 @@ function generateState() {
|
|
|
241
241
|
crypto.getRandomValues(array);
|
|
242
242
|
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
243
243
|
}
|
|
244
|
-
function redirectToHostedAuth(options) {
|
|
244
|
+
async function redirectToHostedAuth(options) {
|
|
245
245
|
const {
|
|
246
246
|
clientId,
|
|
247
247
|
redirectUri,
|
|
@@ -249,10 +249,22 @@ function redirectToHostedAuth(options) {
|
|
|
249
249
|
state = generateState(),
|
|
250
250
|
issuer = "https://api.oauth42.com"
|
|
251
251
|
} = options;
|
|
252
|
+
if (typeof window === "undefined") return;
|
|
253
|
+
if (state.length < 32) throw new Error("OAuth state must contain at least 32 random characters");
|
|
254
|
+
const verifier = generateState();
|
|
255
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
256
|
+
const challenge = btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
257
|
+
const secure = window.location.protocol === "https:";
|
|
258
|
+
const prefix = secure ? "__Host-" : "";
|
|
259
|
+
const attributes = `; Path=/; SameSite=Lax; Max-Age=300${secure ? "; Secure" : ""}`;
|
|
260
|
+
document.cookie = `${prefix}oauth42-state=${encodeURIComponent(state)}${attributes}`;
|
|
261
|
+
document.cookie = `${prefix}oauth42-pkce=${verifier}${attributes}`;
|
|
252
262
|
const params = new URLSearchParams({
|
|
253
263
|
client_id: clientId,
|
|
254
264
|
redirect_uri: redirectUri,
|
|
255
265
|
response_type: "code",
|
|
266
|
+
code_challenge: challenge,
|
|
267
|
+
code_challenge_method: "S256",
|
|
256
268
|
scope,
|
|
257
269
|
state
|
|
258
270
|
});
|
|
@@ -308,7 +320,16 @@ async function loginWithPassword(options) {
|
|
|
308
320
|
success: false,
|
|
309
321
|
error: "mfa_required",
|
|
310
322
|
error_description: "MFA code required",
|
|
311
|
-
requires_mfa: true
|
|
323
|
+
requires_mfa: true,
|
|
324
|
+
mfa_methods: errorData?.mfa_methods,
|
|
325
|
+
email_otp_sent: errorData?.email_otp_sent,
|
|
326
|
+
email_otp_email: errorData?.email_otp_email,
|
|
327
|
+
email_otp_rate_limited: errorData?.email_otp_rate_limited,
|
|
328
|
+
phone_mfa_sent: errorData?.phone_mfa_sent,
|
|
329
|
+
phone_mfa_number: errorData?.phone_mfa_number,
|
|
330
|
+
phone_mfa_method: errorData?.phone_mfa_method,
|
|
331
|
+
phone_mfa_rate_limited: errorData?.phone_mfa_rate_limited,
|
|
332
|
+
phone_mfa_unavailable: errorData?.phone_mfa_unavailable
|
|
312
333
|
};
|
|
313
334
|
}
|
|
314
335
|
if (loginResponse.status === 401 && /invalid mfa code/i.test(desc)) {
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/index.ts","../../src/client/hooks.ts","../../src/client/components.tsx","../../src/utils/hosted-auth.ts","../../src/client/auth.ts"],"sourcesContent":["// Client-side exports\n\n// Re-export commonly used next-auth/react functions\nexport { signIn, signOut, useSession, SessionProvider } from 'next-auth/react';\nexport type { Session } from 'next-auth';\nexport {\n useOAuth42Session,\n useOAuth42User,\n useOAuth42Tokens,\n useRequireAuth,\n} from './hooks';\n\nexport type {\n OAuth42Session,\n UseOAuth42SessionReturn,\n} from './hooks';\n\nexport {\n SignInButton,\n SignOutButton,\n UserProfile,\n AuthStatus,\n ProtectedComponent,\n} from './components';\n\nexport type {\n SignInButtonProps,\n SignOutButtonProps,\n UserProfileProps,\n AuthStatusProps,\n ProtectedComponentProps,\n} from './components';\n\n// Hosted auth utilities\nexport {\n redirectToHostedAuth,\n verifyState,\n DEFAULT_HOSTED_AUTH_CONFIG,\n} from '../utils/hosted-auth';\n\nexport type {\n HostedAuthOptions,\n HostedAuthConfig,\n} from '../utils/hosted-auth';\n\n// Custom authentication utilities\nexport {\n loginWithPassword,\n initiateAuthenticatorLogin,\n completeAuthenticatorLogin,\n simulateApproval,\n logout,\n logoutEverywhere,\n} from './auth';\n\nexport type {\n LoginWithPasswordOptions,\n LoginWithAuthenticatorOptions,\n AuthError,\n} from './auth';","import { useSession, signIn, signOut } from 'next-auth/react';\nimport { useCallback, useEffect, useState } from 'react';\n\nexport type OAuth42Session<E = {}> = ({\n user?: {\n email?: string | null;\n name?: string | null;\n image?: string | null;\n username?: string;\n emailVerified?: boolean;\n };\n accessToken?: string;\n idToken?: string;\n expires?: string;\n}) & E;\n\nexport interface UseOAuth42SessionReturn<E = {}> {\n session: OAuth42Session<E> | null;\n loading: boolean;\n error: Error | null;\n isAuthenticated: boolean;\n signIn: () => Promise<void>;\n signOut: () => Promise<void>;\n}\n\n/**\n * Hook to manage OAuth42 session with optional extra fields\n */\nexport function useOAuth42Session<E = {}>(): UseOAuth42SessionReturn<E> {\n const { data: session, status } = useSession();\n const [error, setError] = useState<Error | null>(null);\n \n const handleSignIn = useCallback(async () => {\n try {\n setError(null);\n await signIn('oauth42');\n } catch (err) {\n setError(err as Error);\n }\n }, []);\n \n const handleSignOut = useCallback(async () => {\n try {\n setError(null);\n await signOut();\n } catch (err) {\n setError(err as Error);\n }\n }, []);\n \n return {\n session: session as unknown as OAuth42Session<E> | null,\n loading: status === 'loading',\n error,\n isAuthenticated: status === 'authenticated',\n signIn: handleSignIn,\n signOut: handleSignOut,\n };\n}\n\n/**\n * Hook to get the current OAuth42 user\n */\nexport function useOAuth42User<E = {}>() {\n const { session, isAuthenticated } = useOAuth42Session<E>();\n \n return {\n user: isAuthenticated ? session?.user : null,\n isAuthenticated,\n };\n}\n\n/**\n * Hook to manage OAuth42 tokens\n */\nexport function useOAuth42Tokens<E = {}>() {\n const { session } = useOAuth42Session<E>();\n const [isExpired, setIsExpired] = useState(false);\n \n useEffect(() => {\n if (session?.expires) {\n const expiryTime = new Date(session.expires).getTime();\n const now = Date.now();\n setIsExpired(now >= expiryTime);\n \n // Set a timer to update expiry status\n const timeUntilExpiry = expiryTime - now;\n if (timeUntilExpiry > 0) {\n const timer = setTimeout(() => {\n setIsExpired(true);\n }, timeUntilExpiry);\n \n return () => clearTimeout(timer);\n }\n }\n }, [session?.expires]);\n \n return {\n accessToken: session?.accessToken,\n idToken: session?.idToken,\n isExpired,\n refreshToken: async () => {\n // Trigger a session refresh\n await signIn('oauth42');\n },\n };\n}\n\n/**\n * Hook for protected routes\n */\nexport function useRequireAuth(redirectTo: string = '/auth/signin') {\n const { isAuthenticated, loading } = useOAuth42Session();\n const [isRedirecting, setIsRedirecting] = useState(false);\n \n useEffect(() => {\n if (!loading && !isAuthenticated && !isRedirecting) {\n setIsRedirecting(true);\n if (typeof window !== 'undefined') {\n window.location.href = redirectTo;\n }\n }\n }, [isAuthenticated, loading, redirectTo, isRedirecting]);\n \n return {\n isAuthenticated,\n loading: loading || isRedirecting,\n };\n}\n","import React from 'react';\nimport { signIn, signOut } from 'next-auth/react';\nimport { useOAuth42Session, useOAuth42User } from './hooks';\n\nexport interface SignInButtonProps {\n children?: React.ReactNode;\n className?: string;\n callbackUrl?: string;\n onClick?: () => void;\n}\n\n/**\n * Sign in button component\n */\nexport function SignInButton({ \n children = 'Sign in with OAuth42', \n className = '',\n callbackUrl = '/',\n onClick\n}: SignInButtonProps) {\n const handleClick = async () => {\n if (onClick) onClick();\n await signIn('oauth42', { callbackUrl });\n };\n \n return (\n <button\n onClick={handleClick}\n className={className}\n type=\"button\"\n >\n {children}\n </button>\n );\n}\n\nexport interface SignOutButtonProps {\n children?: React.ReactNode;\n className?: string;\n callbackUrl?: string;\n onClick?: () => void;\n}\n\n/**\n * Sign out button component\n */\nexport function SignOutButton({ \n children = 'Sign out', \n className = '',\n callbackUrl = '/',\n onClick\n}: SignOutButtonProps) {\n const handleClick = async () => {\n if (onClick) onClick();\n await signOut({ callbackUrl });\n };\n \n return (\n <button\n onClick={handleClick}\n className={className}\n type=\"button\"\n >\n {children}\n </button>\n );\n}\n\nexport interface UserProfileProps {\n className?: string;\n showEmail?: boolean;\n showName?: boolean;\n showImage?: boolean;\n loadingComponent?: React.ReactNode;\n notAuthenticatedComponent?: React.ReactNode;\n}\n\n/**\n * User profile display component\n */\nexport function UserProfile({\n className = '',\n showEmail = true,\n showName = true,\n showImage = true,\n loadingComponent = <div>Loading...</div>,\n notAuthenticatedComponent = <div>Not authenticated</div>,\n}: UserProfileProps) {\n const { session, loading, isAuthenticated } = useOAuth42Session();\n \n if (loading) {\n return <>{loadingComponent}</>;\n }\n \n if (!isAuthenticated || !session?.user) {\n return <>{notAuthenticatedComponent}</>;\n }\n \n const { user } = session;\n \n return (\n <div className={className}>\n {showImage && user.image && (\n <img \n src={user.image} \n alt={user.name || 'User'} \n style={{ width: 50, height: 50, borderRadius: '50%' }}\n />\n )}\n {showName && user.name && <div>{user.name}</div>}\n {showEmail && user.email && <div>{user.email}</div>}\n </div>\n );\n}\n\nexport interface AuthStatusProps {\n authenticatedComponent?: React.ReactNode;\n unauthenticatedComponent?: React.ReactNode;\n loadingComponent?: React.ReactNode;\n}\n\n/**\n * Conditional rendering based on auth status\n */\nexport function AuthStatus({\n authenticatedComponent,\n unauthenticatedComponent,\n loadingComponent = <div>Loading...</div>,\n}: AuthStatusProps) {\n const { isAuthenticated, loading } = useOAuth42Session();\n \n if (loading) {\n return <>{loadingComponent}</>;\n }\n \n return <>{isAuthenticated ? authenticatedComponent : unauthenticatedComponent}</>;\n}\n\nexport interface ProtectedComponentProps {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n loadingComponent?: React.ReactNode;\n}\n\n/**\n * Wrapper component for protected content\n */\nexport function ProtectedComponent({\n children,\n fallback = <SignInButton />,\n loadingComponent = <div>Loading...</div>,\n}: ProtectedComponentProps) {\n const { isAuthenticated, loading } = useOAuth42Session();\n \n if (loading) {\n return <>{loadingComponent}</>;\n }\n \n if (!isAuthenticated) {\n return <>{fallback}</>;\n }\n \n return <>{children}</>;\n}","/**\n * Utilities for OAuth42 Hosted Authentication\n */\n\nexport interface HostedAuthOptions {\n /** OAuth2 client ID */\n clientId: string;\n /** Redirect URI after authentication */\n redirectUri: string;\n /** OAuth2 scopes (space-separated) */\n scope?: string;\n /** OAuth2 state parameter for CSRF protection */\n state?: string;\n /** Base URL for OAuth42 issuer/API (defaults to production) */\n issuer?: string;\n}\n\n/**\n * Generate a random state parameter for CSRF protection\n */\nfunction generateState(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Redirect to OAuth42 hosted authentication pages\n *\n * @example\n * ```ts\n * import { redirectToHostedAuth } from '@oauth42/next/client';\n *\n * function LoginButton() {\n * return (\n * <button onClick={() => redirectToHostedAuth({\n * clientId: process.env.NEXT_PUBLIC_OAUTH42_CLIENT_ID!,\n * redirectUri: `${window.location.origin}/api/auth/callback`,\n * })}>\n * Sign in with OAuth42\n * </button>\n * );\n * }\n * ```\n */\nexport function redirectToHostedAuth(options: HostedAuthOptions): void {\n const {\n clientId,\n redirectUri,\n scope = 'openid profile email',\n state = generateState(),\n issuer = 'https://api.oauth42.com',\n } = options;\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n scope,\n state,\n });\n\n // Redirect to the backend's authorize endpoint, which handles the OAuth flow\n // and redirects to hosted auth login if user is not authenticated\n const authUrl = `${issuer}/oauth2/authorize?${params.toString()}`;\n\n // Store state in sessionStorage for verification on callback\n if (typeof window !== 'undefined') {\n sessionStorage.setItem('oauth42_state', state);\n window.location.href = authUrl;\n }\n}\n\n/**\n * Verify state parameter on OAuth2 callback\n * Call this in your callback page to verify the state matches\n *\n * @example\n * ```ts\n * import { verifyState } from '@oauth42/next/client';\n *\n * export default function CallbackPage() {\n * const searchParams = useSearchParams();\n * const state = searchParams.get('state');\n *\n * if (!verifyState(state)) {\n * return <div>Invalid state parameter</div>;\n * }\n *\n * // Continue with token exchange...\n * }\n * ```\n */\nexport function verifyState(state: string | null): boolean {\n if (typeof window === 'undefined') return false;\n if (!state) return false;\n\n const storedState = sessionStorage.getItem('oauth42_state');\n sessionStorage.removeItem('oauth42_state');\n\n return storedState === state;\n}\n\n/**\n * Configuration for hosted authentication\n */\nexport interface HostedAuthConfig {\n /** Enable hosted authentication */\n enabled: boolean;\n /** Base URL for hosted auth pages (optional, defaults to production) */\n baseUrl?: string;\n /** Feature flags for hosted auth */\n features?: {\n /** Allow user signup */\n signup?: boolean;\n /** Allow social login */\n socialLogin?: boolean;\n /** Allow password reset */\n passwordReset?: boolean;\n };\n}\n\n/**\n * Default hosted auth configuration\n */\nexport const DEFAULT_HOSTED_AUTH_CONFIG: HostedAuthConfig = {\n enabled: true,\n baseUrl: 'https://auth.oauth42.com',\n features: {\n signup: true,\n socialLogin: false,\n passwordReset: true,\n },\n};\n","/**\n * OAuth42 Custom Authentication Utilities\n *\n * Provides functions for implementing custom login UIs in customer apps\n * while properly handling OAuth2 PKCE flows and next-auth integration.\n */\n\nimport { signIn } from 'next-auth/react';\n\n// PKCE utilities\nfunction base64URLEncode(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n}\n\nasync function generateCodeVerifier(): Promise<string> {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64URLEncode(array.buffer);\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return base64URLEncode(hash);\n}\n\nfunction generateState(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64URLEncode(array.buffer);\n}\n\nexport interface LoginWithPasswordOptions {\n email: string;\n password: string;\n mfaCode?: string;\n /**\n * URL to redirect to after successful authentication.\n * If not provided, will redirect to '/'\n */\n callbackUrl?: string;\n}\n\nexport interface LoginWithAuthenticatorOptions {\n email: string;\n sessionId: string;\n /**\n * URL to redirect to after successful authentication.\n * If not provided, will redirect to '/'\n */\n callbackUrl?: string;\n}\n\nexport interface AuthError {\n error: string;\n error_description?: string;\n requires_enrollment?: boolean;\n enrollment_token?: string;\n requires_mfa?: boolean;\n}\n\n/**\n * Authenticate with email/password and complete OAuth PKCE flow\n *\n * This function handles the full authentication flow:\n * 1. Authenticates with the backend using credentials\n * 2. Uses the access token to authorize the OAuth client\n * 3. Exchanges authorization code for tokens via next-auth\n *\n * @example\n * ```tsx\n * const result = await loginWithPassword({\n * email: 'user@example.com',\n * password: 'password123',\n * callbackUrl: '/dashboard'\n * });\n *\n * if (result.success) {\n * // User is authenticated, next-auth session is set\n * } else if (result.requires_mfa) {\n * // Prompt for MFA code and call again with mfaCode\n * }\n * ```\n */\nexport async function loginWithPassword(\n options: LoginWithPasswordOptions\n): Promise<{ success: boolean; access_token?: string } & Partial<AuthError>> {\n try {\n // Step 1: Authenticate with backend to get access token\n const loginResponse = await fetch('/api/auth/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email: options.email,\n password: options.password,\n ...(options.mfaCode ? { mfa_code: options.mfaCode } : {}),\n }),\n });\n\n if (!loginResponse.ok) {\n const errorData = await loginResponse.json().catch(() => ({}));\n const desc: string = errorData?.error_description || errorData?.message || '';\n\n // Handle special cases\n if (loginResponse.status === 403 && errorData?.requires_enrollment) {\n return {\n success: false,\n error: 'enrollment_required',\n error_description: 'MFA enrollment required',\n requires_enrollment: true,\n enrollment_token: errorData.enrollment_token,\n };\n }\n\n if (loginResponse.status === 403 && /mfa required/i.test(desc)) {\n return {\n success: false,\n error: 'mfa_required',\n error_description: 'MFA code required',\n requires_mfa: true,\n };\n }\n\n if (loginResponse.status === 401 && /invalid mfa code/i.test(desc)) {\n return {\n success: false,\n error: 'invalid_mfa_code',\n error_description: 'Invalid MFA code',\n requires_mfa: true,\n };\n }\n\n return {\n success: false,\n error: 'authentication_failed',\n error_description: desc || 'Invalid credentials',\n };\n }\n\n const { access_token } = await loginResponse.json();\n\n // Return access token for the application to use in OAuth authorize flow\n return { success: true, access_token };\n } catch (error) {\n console.error('Login error:', error);\n return {\n success: false,\n error: 'network_error',\n error_description: 'Failed to connect to authentication server',\n };\n }\n}\n\n/**\n * Initiate authenticator (passwordless) login and return challenge code\n *\n * @example\n * ```tsx\n * const result = await initiateAuthenticatorLogin({\n * email: 'user@example.com',\n * sessionId: crypto.randomUUID()\n * });\n *\n * if (result.success) {\n * // Display result.challengeCode to user\n * // Wait for WebSocket approval or poll for completion\n * }\n * ```\n */\nexport async function initiateAuthenticatorLogin(\n options: LoginWithAuthenticatorOptions\n): Promise<{\n success: boolean;\n challengeCode?: string;\n challengeId?: string;\n} & Partial<AuthError>> {\n try {\n const response = await fetch('/api/auth/passwordless/initiate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email: options.email,\n session_id: options.sessionId,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n return {\n success: false,\n error: 'initiation_failed',\n error_description: errorData.error_description || 'Failed to create challenge',\n };\n }\n\n const data = await response.json();\n\n return {\n success: true,\n challengeCode: data.challenge_code,\n challengeId: data.challenge_id,\n };\n } catch (error) {\n console.error('Authenticator login error:', error);\n return {\n success: false,\n error: 'network_error',\n error_description: 'Failed to connect to authentication server',\n };\n }\n}\n\n/**\n * Complete authenticator login after approval\n *\n * Call this after receiving approval notification via WebSocket\n * or after the user has approved on their mobile device.\n * Returns the access token for the application to use in OAuth authorize flow.\n *\n * @param accessToken - Access token received from approval payload\n */\nexport async function completeAuthenticatorLogin(\n accessToken: string\n): Promise<{ success: boolean; access_token: string }> {\n return { success: true, access_token: accessToken };\n}\n\n/**\n * Simulate approval for testing (development only)\n *\n * @param challengeId - The challenge ID to approve\n * @param selectedCode - The code that was displayed to the user\n */\nexport async function simulateApproval(\n challengeId: string,\n selectedCode: string\n): Promise<{ success: boolean }> {\n try {\n await fetch('/api/auth/challenge/approve', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n challenge_id: challengeId,\n selected_code: selectedCode,\n }),\n });\n\n return { success: true };\n } catch (error) {\n console.error('Simulated approval failed:', error);\n return { success: false };\n }\n}\n\n/**\n * Logout the current user from the app only (app-level logout)\n *\n * This clears the next-auth session for this app AND removes the app-user\n * mapping from the session registry. On next login, the user will see\n * the account picker to choose which account to use.\n *\n * For provider-level logout (sign out of all apps), use logoutEverywhere().\n *\n * @param options.callbackUrl - URL to redirect to after logout (default: '/')\n * @param options.clientId - OAuth client ID to clear from registry\n * @param options.issuer - OAuth42 issuer URL for the logout endpoint\n *\n * @example\n * ```tsx\n * import { logout } from '@oauth42/next/client';\n *\n * const handleLogout = async () => {\n * await logout({\n * callbackUrl: '/login',\n * clientId: process.env.NEXT_PUBLIC_OAUTH42_CLIENT_ID,\n * issuer: process.env.NEXT_PUBLIC_OAUTH_ISSUER || 'https://localhost:8443'\n * });\n * };\n * ```\n */\nexport async function logout(options?: {\n callbackUrl?: string;\n clientId?: string;\n issuer?: string;\n}): Promise<void> {\n // If clientId and issuer provided, call backend to clear app_user from registry\n if (options?.clientId && options?.issuer) {\n try {\n await fetch(`${options.issuer}/oauth2/logout?client_id=${encodeURIComponent(options.clientId)}`, {\n method: 'POST',\n credentials: 'include', // Include cookies (registry cookie)\n });\n } catch (err) {\n console.warn('[OAuth42] Failed to clear app user from registry:', err);\n // Continue with NextAuth signOut even if this fails\n }\n }\n\n // Import signOut from next-auth/react\n const { signOut } = await import('next-auth/react');\n\n // Clear next-auth session (app-level only)\n await signOut({\n callbackUrl: options?.callbackUrl || '/',\n redirect: true\n });\n}\n\n/**\n * Logout the current user from ALL apps using OAuth42 (provider-level logout)\n *\n * This redirects to the OAuth42 provider's logout endpoint to clear the\n * oauth42_session cookie, effectively logging the user out of all apps.\n *\n * Use this when testing fresh login flows or when the user explicitly\n * wants to sign out of everything.\n *\n * @param issuer - The OAuth42 issuer URL (e.g., 'https://localhost:8443')\n * @param callbackUrl - URL to redirect to after logout (default: current origin)\n *\n * @example\n * ```tsx\n * import { logoutEverywhere } from '@oauth42/next/client';\n *\n * const handleLogoutEverywhere = () => {\n * const issuer = process.env.NEXT_PUBLIC_OAUTH42_ISSUER || 'https://localhost:8443';\n * logoutEverywhere(issuer, '/auth/signin');\n * };\n * ```\n */\nexport async function logoutEverywhere(issuer: string, callbackUrl?: string): Promise<void> {\n const redirectUri = callbackUrl || window.location.origin;\n\n // Build the final redirect URL - backend will clear oauth42_session, then redirect\n // to NextAuth signout which will clear the app session cookie\n const backendLogoutUrl = `${issuer}/auth/logout?redirect_uri=${encodeURIComponent(redirectUri)}`;\n\n // First call NextAuth signout to clear the app session cookie\n // Then redirect to backend to clear the oauth42_session cookie\n try {\n // Get CSRF token from NextAuth\n const csrfResponse = await fetch('/api/auth/csrf');\n const { csrfToken } = await csrfResponse.json();\n\n // Call NextAuth signOut endpoint to clear session - wait for it to complete\n const signoutResponse = await fetch('/api/auth/signout', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n csrfToken,\n json: 'true',\n }),\n credentials: 'include',\n });\n\n // Log response for debugging\n console.log('[OAuth42] NextAuth signout response:', signoutResponse.status);\n } catch (err) {\n console.warn('[OAuth42] Failed to call NextAuth signout:', err);\n }\n\n // Now redirect to OAuth42 provider logout endpoint\n // This clears the oauth42_session cookie and redirects back to the app\n console.log('[OAuth42] Redirecting to backend logout:', backendLogoutUrl);\n window.location.href = backendLogoutUrl;\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","signOut"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts","../../src/client/hooks.ts","../../src/client/components.tsx","../../src/utils/hosted-auth.ts","../../src/client/auth.ts"],"sourcesContent":["// Client-side exports\n\n// Re-export commonly used next-auth/react functions\nexport { signIn, signOut, useSession, SessionProvider } from 'next-auth/react';\nexport type { Session } from 'next-auth';\nexport {\n useOAuth42Session,\n useOAuth42User,\n useOAuth42Tokens,\n useRequireAuth,\n} from './hooks';\n\nexport type {\n OAuth42Session,\n UseOAuth42SessionReturn,\n} from './hooks';\n\nexport {\n SignInButton,\n SignOutButton,\n UserProfile,\n AuthStatus,\n ProtectedComponent,\n} from './components';\n\nexport type {\n SignInButtonProps,\n SignOutButtonProps,\n UserProfileProps,\n AuthStatusProps,\n ProtectedComponentProps,\n} from './components';\n\n// Hosted auth utilities\nexport {\n redirectToHostedAuth,\n verifyState,\n DEFAULT_HOSTED_AUTH_CONFIG,\n} from '../utils/hosted-auth';\n\nexport type {\n HostedAuthOptions,\n HostedAuthConfig,\n} from '../utils/hosted-auth';\n\n// Custom authentication utilities\nexport {\n loginWithPassword,\n initiateAuthenticatorLogin,\n completeAuthenticatorLogin,\n simulateApproval,\n logout,\n logoutEverywhere,\n} from './auth';\n\nexport type {\n LoginWithPasswordOptions,\n LoginWithAuthenticatorOptions,\n AuthError,\n} from './auth';","import { useSession, signIn, signOut } from 'next-auth/react';\nimport { useCallback, useEffect, useState } from 'react';\n\nexport type OAuth42Session<E extends object = Record<string, unknown>> = ({\n user?: {\n email?: string | null;\n name?: string | null;\n image?: string | null;\n username?: string;\n emailVerified?: boolean;\n };\n accessToken?: string;\n idToken?: string;\n expires?: string;\n}) & E;\n\nexport interface UseOAuth42SessionReturn<E extends object = Record<string, unknown>> {\n session: OAuth42Session<E> | null;\n loading: boolean;\n error: Error | null;\n isAuthenticated: boolean;\n signIn: () => Promise<void>;\n signOut: () => Promise<void>;\n}\n\n/**\n * Hook to manage OAuth42 session with optional extra fields\n */\nexport function useOAuth42Session<E extends object = Record<string, unknown>>(): UseOAuth42SessionReturn<E> {\n const { data: session, status } = useSession();\n const [error, setError] = useState<Error | null>(null);\n \n const handleSignIn = useCallback(async () => {\n try {\n setError(null);\n await signIn('oauth42');\n } catch (err) {\n setError(err as Error);\n }\n }, []);\n \n const handleSignOut = useCallback(async () => {\n try {\n setError(null);\n await signOut();\n } catch (err) {\n setError(err as Error);\n }\n }, []);\n \n return {\n session: session as unknown as OAuth42Session<E> | null,\n loading: status === 'loading',\n error,\n isAuthenticated: status === 'authenticated',\n signIn: handleSignIn,\n signOut: handleSignOut,\n };\n}\n\n/**\n * Hook to get the current OAuth42 user\n */\nexport function useOAuth42User<E extends object = Record<string, unknown>>() {\n const { session, isAuthenticated } = useOAuth42Session<E>();\n \n return {\n user: isAuthenticated ? session?.user : null,\n isAuthenticated,\n };\n}\n\n/**\n * Hook to manage OAuth42 tokens\n */\nexport function useOAuth42Tokens<E extends object = Record<string, unknown>>() {\n const { session } = useOAuth42Session<E>();\n const [isExpired, setIsExpired] = useState(false);\n \n useEffect(() => {\n if (session?.expires) {\n const expiryTime = new Date(session.expires).getTime();\n const now = Date.now();\n setIsExpired(now >= expiryTime);\n \n // Set a timer to update expiry status\n const timeUntilExpiry = expiryTime - now;\n if (timeUntilExpiry > 0) {\n const timer = setTimeout(() => {\n setIsExpired(true);\n }, timeUntilExpiry);\n \n return () => clearTimeout(timer);\n }\n }\n }, [session?.expires]);\n \n return {\n accessToken: session?.accessToken,\n idToken: session?.idToken,\n isExpired,\n refreshToken: async () => {\n // Trigger a session refresh\n await signIn('oauth42');\n },\n };\n}\n\n/**\n * Hook for protected routes\n */\nexport function useRequireAuth(redirectTo: string = '/auth/signin') {\n const { isAuthenticated, loading } = useOAuth42Session();\n const [isRedirecting, setIsRedirecting] = useState(false);\n \n useEffect(() => {\n if (!loading && !isAuthenticated && !isRedirecting) {\n setIsRedirecting(true);\n if (typeof window !== 'undefined') {\n window.location.href = redirectTo;\n }\n }\n }, [isAuthenticated, loading, redirectTo, isRedirecting]);\n \n return {\n isAuthenticated,\n loading: loading || isRedirecting,\n };\n}\n","import React from 'react';\nimport { signIn, signOut } from 'next-auth/react';\nimport { useOAuth42Session, useOAuth42User } from './hooks';\n\nexport interface SignInButtonProps {\n children?: React.ReactNode;\n className?: string;\n callbackUrl?: string;\n onClick?: () => void;\n}\n\n/**\n * Sign in button component\n */\nexport function SignInButton({ \n children = 'Sign in with OAuth42', \n className = '',\n callbackUrl = '/',\n onClick\n}: SignInButtonProps) {\n const handleClick = async () => {\n if (onClick) onClick();\n await signIn('oauth42', { callbackUrl });\n };\n \n return (\n <button\n onClick={handleClick}\n className={className}\n type=\"button\"\n >\n {children}\n </button>\n );\n}\n\nexport interface SignOutButtonProps {\n children?: React.ReactNode;\n className?: string;\n callbackUrl?: string;\n onClick?: () => void;\n}\n\n/**\n * Sign out button component\n */\nexport function SignOutButton({ \n children = 'Sign out', \n className = '',\n callbackUrl = '/',\n onClick\n}: SignOutButtonProps) {\n const handleClick = async () => {\n if (onClick) onClick();\n await signOut({ callbackUrl });\n };\n \n return (\n <button\n onClick={handleClick}\n className={className}\n type=\"button\"\n >\n {children}\n </button>\n );\n}\n\nexport interface UserProfileProps {\n className?: string;\n showEmail?: boolean;\n showName?: boolean;\n showImage?: boolean;\n loadingComponent?: React.ReactNode;\n notAuthenticatedComponent?: React.ReactNode;\n}\n\n/**\n * User profile display component\n */\nexport function UserProfile({\n className = '',\n showEmail = true,\n showName = true,\n showImage = true,\n loadingComponent = <div>Loading...</div>,\n notAuthenticatedComponent = <div>Not authenticated</div>,\n}: UserProfileProps) {\n const { session, loading, isAuthenticated } = useOAuth42Session();\n \n if (loading) {\n return <>{loadingComponent}</>;\n }\n \n if (!isAuthenticated || !session?.user) {\n return <>{notAuthenticatedComponent}</>;\n }\n \n const { user } = session;\n \n return (\n <div className={className}>\n {showImage && user.image && (\n <img \n src={user.image} \n alt={user.name || 'User'} \n style={{ width: 50, height: 50, borderRadius: '50%' }}\n />\n )}\n {showName && user.name && <div>{user.name}</div>}\n {showEmail && user.email && <div>{user.email}</div>}\n </div>\n );\n}\n\nexport interface AuthStatusProps {\n authenticatedComponent?: React.ReactNode;\n unauthenticatedComponent?: React.ReactNode;\n loadingComponent?: React.ReactNode;\n}\n\n/**\n * Conditional rendering based on auth status\n */\nexport function AuthStatus({\n authenticatedComponent,\n unauthenticatedComponent,\n loadingComponent = <div>Loading...</div>,\n}: AuthStatusProps) {\n const { isAuthenticated, loading } = useOAuth42Session();\n \n if (loading) {\n return <>{loadingComponent}</>;\n }\n \n return <>{isAuthenticated ? authenticatedComponent : unauthenticatedComponent}</>;\n}\n\nexport interface ProtectedComponentProps {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n loadingComponent?: React.ReactNode;\n}\n\n/**\n * Wrapper component for protected content\n */\nexport function ProtectedComponent({\n children,\n fallback = <SignInButton />,\n loadingComponent = <div>Loading...</div>,\n}: ProtectedComponentProps) {\n const { isAuthenticated, loading } = useOAuth42Session();\n \n if (loading) {\n return <>{loadingComponent}</>;\n }\n \n if (!isAuthenticated) {\n return <>{fallback}</>;\n }\n \n return <>{children}</>;\n}","/**\n * Utilities for OAuth42 Hosted Authentication\n */\n\nexport interface HostedAuthOptions {\n /** OAuth2 client ID */\n clientId: string;\n /** Redirect URI after authentication */\n redirectUri: string;\n /** OAuth2 scopes (space-separated) */\n scope?: string;\n /** OAuth2 state parameter for CSRF protection */\n state?: string;\n /** Base URL for OAuth42 issuer/API (defaults to production) */\n issuer?: string;\n}\n\n/**\n * Generate a random state parameter for CSRF protection\n */\nfunction generateState(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Redirect to OAuth42 hosted authentication pages\n *\n * @example\n * ```ts\n * import { redirectToHostedAuth } from '@oauth42/next/client';\n *\n * function LoginButton() {\n * return (\n * <button onClick={() => redirectToHostedAuth({\n * clientId: process.env.NEXT_PUBLIC_OAUTH42_CLIENT_ID!,\n * redirectUri: `${window.location.origin}/api/auth/callback`,\n * })}>\n * Sign in with OAuth42\n * </button>\n * );\n * }\n * ```\n */\nexport async function redirectToHostedAuth(options: HostedAuthOptions): Promise<void> {\n const {\n clientId,\n redirectUri,\n scope = 'openid profile email',\n state = generateState(),\n issuer = 'https://api.oauth42.com',\n } = options;\n\n if (typeof window === 'undefined') return;\n if (state.length < 32) throw new Error('OAuth state must contain at least 32 random characters');\n const verifier = generateState();\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n const challenge = btoa(String.fromCharCode(...new Uint8Array(digest)))\n .replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n const secure = window.location.protocol === 'https:';\n const prefix = secure ? '__Host-' : '';\n const attributes = `; Path=/; SameSite=Lax; Max-Age=300${secure ? '; Secure' : ''}`;\n document.cookie = `${prefix}oauth42-state=${encodeURIComponent(state)}${attributes}`;\n document.cookie = `${prefix}oauth42-pkce=${verifier}${attributes}`;\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n code_challenge: challenge,\n code_challenge_method: 'S256',\n scope,\n state,\n });\n\n // Redirect to the backend's authorize endpoint, which handles the OAuth flow\n // and redirects to hosted auth login if user is not authenticated\n const authUrl = `${issuer}/oauth2/authorize?${params.toString()}`;\n\n // Store state in sessionStorage for verification on callback\n if (typeof window !== 'undefined') {\n sessionStorage.setItem('oauth42_state', state);\n window.location.href = authUrl;\n }\n}\n\n/**\n * Verify state parameter on OAuth2 callback\n * Call this in your callback page to verify the state matches\n *\n * @example\n * ```ts\n * import { verifyState } from '@oauth42/next/client';\n *\n * export default function CallbackPage() {\n * const searchParams = useSearchParams();\n * const state = searchParams.get('state');\n *\n * if (!verifyState(state)) {\n * return <div>Invalid state parameter</div>;\n * }\n *\n * // Continue with token exchange...\n * }\n * ```\n */\nexport function verifyState(state: string | null): boolean {\n if (typeof window === 'undefined') return false;\n if (!state) return false;\n\n const storedState = sessionStorage.getItem('oauth42_state');\n sessionStorage.removeItem('oauth42_state');\n\n return storedState === state;\n}\n\n/**\n * Configuration for hosted authentication\n */\nexport interface HostedAuthConfig {\n /** Enable hosted authentication */\n enabled: boolean;\n /** Base URL for hosted auth pages (optional, defaults to production) */\n baseUrl?: string;\n /** Feature flags for hosted auth */\n features?: {\n /** Allow user signup */\n signup?: boolean;\n /** Allow social login */\n socialLogin?: boolean;\n /** Allow password reset */\n passwordReset?: boolean;\n };\n}\n\n/**\n * Default hosted auth configuration\n */\nexport const DEFAULT_HOSTED_AUTH_CONFIG: HostedAuthConfig = {\n enabled: true,\n baseUrl: 'https://auth.oauth42.com',\n features: {\n signup: true,\n socialLogin: false,\n passwordReset: true,\n },\n};\n","/**\n * OAuth42 Custom Authentication Utilities\n *\n * Provides functions for implementing custom login UIs in customer apps\n * while properly handling OAuth2 PKCE flows and next-auth integration.\n */\n\nimport { signIn } from 'next-auth/react';\n\n// PKCE utilities\nfunction base64URLEncode(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n}\n\nasync function generateCodeVerifier(): Promise<string> {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64URLEncode(array.buffer);\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return base64URLEncode(hash);\n}\n\nfunction generateState(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64URLEncode(array.buffer);\n}\n\nexport interface LoginWithPasswordOptions {\n email: string;\n password: string;\n mfaCode?: string;\n /**\n * URL to redirect to after successful authentication.\n * If not provided, will redirect to '/'\n */\n callbackUrl?: string;\n}\n\nexport interface LoginWithAuthenticatorOptions {\n email: string;\n sessionId: string;\n /**\n * URL to redirect to after successful authentication.\n * If not provided, will redirect to '/'\n */\n callbackUrl?: string;\n}\n\nexport interface AuthError {\n error: string;\n error_description?: string;\n requires_enrollment?: boolean;\n enrollment_token?: string;\n requires_mfa?: boolean;\n mfa_methods?: string[];\n email_otp_sent?: boolean;\n email_otp_email?: string;\n email_otp_rate_limited?: boolean;\n phone_mfa_sent?: boolean;\n phone_mfa_number?: string;\n phone_mfa_method?: 'sms' | 'voice';\n phone_mfa_rate_limited?: boolean;\n phone_mfa_unavailable?: boolean;\n}\n\n/**\n * Authenticate with email/password and complete OAuth PKCE flow\n *\n * This function handles the full authentication flow:\n * 1. Authenticates with the backend using credentials\n * 2. Uses the access token to authorize the OAuth client\n * 3. Exchanges authorization code for tokens via next-auth\n *\n * @example\n * ```tsx\n * const result = await loginWithPassword({\n * email: 'user@example.com',\n * password: 'password123',\n * callbackUrl: '/dashboard'\n * });\n *\n * if (result.success) {\n * // User is authenticated, next-auth session is set\n * } else if (result.requires_mfa) {\n * // Prompt for MFA code and call again with mfaCode\n * }\n * ```\n */\nexport async function loginWithPassword(\n options: LoginWithPasswordOptions\n): Promise<{ success: boolean; access_token?: string } & Partial<AuthError>> {\n try {\n // Step 1: Authenticate with backend to get access token\n const loginResponse = await fetch('/api/auth/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email: options.email,\n password: options.password,\n ...(options.mfaCode ? { mfa_code: options.mfaCode } : {}),\n }),\n });\n\n if (!loginResponse.ok) {\n const errorData = await loginResponse.json().catch(() => ({}));\n const desc: string = errorData?.error_description || errorData?.message || '';\n\n // Handle special cases\n if (loginResponse.status === 403 && errorData?.requires_enrollment) {\n return {\n success: false,\n error: 'enrollment_required',\n error_description: 'MFA enrollment required',\n requires_enrollment: true,\n enrollment_token: errorData.enrollment_token,\n };\n }\n\n if (loginResponse.status === 403 && /mfa required/i.test(desc)) {\n return {\n success: false,\n error: 'mfa_required',\n error_description: 'MFA code required',\n requires_mfa: true,\n mfa_methods: errorData?.mfa_methods,\n email_otp_sent: errorData?.email_otp_sent,\n email_otp_email: errorData?.email_otp_email,\n email_otp_rate_limited: errorData?.email_otp_rate_limited,\n phone_mfa_sent: errorData?.phone_mfa_sent,\n phone_mfa_number: errorData?.phone_mfa_number,\n phone_mfa_method: errorData?.phone_mfa_method,\n phone_mfa_rate_limited: errorData?.phone_mfa_rate_limited,\n phone_mfa_unavailable: errorData?.phone_mfa_unavailable,\n };\n }\n\n if (loginResponse.status === 401 && /invalid mfa code/i.test(desc)) {\n return {\n success: false,\n error: 'invalid_mfa_code',\n error_description: 'Invalid MFA code',\n requires_mfa: true,\n };\n }\n\n return {\n success: false,\n error: 'authentication_failed',\n error_description: desc || 'Invalid credentials',\n };\n }\n\n const { access_token } = await loginResponse.json();\n\n // Return access token for the application to use in OAuth authorize flow\n return { success: true, access_token };\n } catch (error) {\n console.error('Login error:', error);\n return {\n success: false,\n error: 'network_error',\n error_description: 'Failed to connect to authentication server',\n };\n }\n}\n\n/**\n * Initiate authenticator (passwordless) login and return challenge code\n *\n * @example\n * ```tsx\n * const result = await initiateAuthenticatorLogin({\n * email: 'user@example.com',\n * sessionId: crypto.randomUUID()\n * });\n *\n * if (result.success) {\n * // Display result.challengeCode to user\n * // Wait for WebSocket approval or poll for completion\n * }\n * ```\n */\nexport async function initiateAuthenticatorLogin(\n options: LoginWithAuthenticatorOptions\n): Promise<{\n success: boolean;\n challengeCode?: string;\n challengeId?: string;\n} & Partial<AuthError>> {\n try {\n const response = await fetch('/api/auth/passwordless/initiate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email: options.email,\n session_id: options.sessionId,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n return {\n success: false,\n error: 'initiation_failed',\n error_description: errorData.error_description || 'Failed to create challenge',\n };\n }\n\n const data = await response.json();\n\n return {\n success: true,\n challengeCode: data.challenge_code,\n challengeId: data.challenge_id,\n };\n } catch (error) {\n console.error('Authenticator login error:', error);\n return {\n success: false,\n error: 'network_error',\n error_description: 'Failed to connect to authentication server',\n };\n }\n}\n\n/**\n * Complete authenticator login after approval\n *\n * Call this after receiving approval notification via WebSocket\n * or after the user has approved on their mobile device.\n * Returns the access token for the application to use in OAuth authorize flow.\n *\n * @param accessToken - Access token received from approval payload\n */\nexport async function completeAuthenticatorLogin(\n accessToken: string\n): Promise<{ success: boolean; access_token: string }> {\n return { success: true, access_token: accessToken };\n}\n\n/**\n * Simulate approval for testing (development only)\n *\n * @param challengeId - The challenge ID to approve\n * @param selectedCode - The code that was displayed to the user\n */\nexport async function simulateApproval(\n challengeId: string,\n selectedCode: string\n): Promise<{ success: boolean }> {\n try {\n await fetch('/api/auth/challenge/approve', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n challenge_id: challengeId,\n selected_code: selectedCode,\n }),\n });\n\n return { success: true };\n } catch (error) {\n console.error('Simulated approval failed:', error);\n return { success: false };\n }\n}\n\n/**\n * Logout the current user from the app only (app-level logout)\n *\n * This clears the next-auth session for this app AND removes the app-user\n * mapping from the session registry. On next login, the user will see\n * the account picker to choose which account to use.\n *\n * For provider-level logout (sign out of all apps), use logoutEverywhere().\n *\n * @param options.callbackUrl - URL to redirect to after logout (default: '/')\n * @param options.clientId - OAuth client ID to clear from registry\n * @param options.issuer - OAuth42 issuer URL for the logout endpoint\n *\n * @example\n * ```tsx\n * import { logout } from '@oauth42/next/client';\n *\n * const handleLogout = async () => {\n * await logout({\n * callbackUrl: '/login',\n * clientId: process.env.NEXT_PUBLIC_OAUTH42_CLIENT_ID,\n * issuer: process.env.NEXT_PUBLIC_OAUTH_ISSUER || 'https://localhost:8443'\n * });\n * };\n * ```\n */\nexport async function logout(options?: {\n callbackUrl?: string;\n clientId?: string;\n issuer?: string;\n}): Promise<void> {\n // If clientId and issuer provided, call backend to clear app_user from registry\n if (options?.clientId && options?.issuer) {\n try {\n await fetch(`${options.issuer}/oauth2/logout?client_id=${encodeURIComponent(options.clientId)}`, {\n method: 'POST',\n credentials: 'include', // Include cookies (registry cookie)\n });\n } catch (err) {\n console.warn('[OAuth42] Failed to clear app user from registry:', err);\n // Continue with NextAuth signOut even if this fails\n }\n }\n\n // Import signOut from next-auth/react\n const { signOut } = await import('next-auth/react');\n\n // Clear next-auth session (app-level only)\n await signOut({\n callbackUrl: options?.callbackUrl || '/',\n redirect: true\n });\n}\n\n/**\n * Logout the current user from ALL apps using OAuth42 (provider-level logout)\n *\n * This redirects to the OAuth42 provider's logout endpoint to clear the\n * oauth42_session cookie, effectively logging the user out of all apps.\n *\n * Use this when testing fresh login flows or when the user explicitly\n * wants to sign out of everything.\n *\n * @param issuer - The OAuth42 issuer URL (e.g., 'https://localhost:8443')\n * @param callbackUrl - URL to redirect to after logout (default: current origin)\n *\n * @example\n * ```tsx\n * import { logoutEverywhere } from '@oauth42/next/client';\n *\n * const handleLogoutEverywhere = () => {\n * const issuer = process.env.NEXT_PUBLIC_OAUTH42_ISSUER || 'https://localhost:8443';\n * logoutEverywhere(issuer, '/auth/signin');\n * };\n * ```\n */\nexport async function logoutEverywhere(issuer: string, callbackUrl?: string): Promise<void> {\n const redirectUri = callbackUrl || window.location.origin;\n\n // Build the final redirect URL - backend will clear oauth42_session, then redirect\n // to NextAuth signout which will clear the app session cookie\n const backendLogoutUrl = `${issuer}/auth/logout?redirect_uri=${encodeURIComponent(redirectUri)}`;\n\n // First call NextAuth signout to clear the app session cookie\n // Then redirect to backend to clear the oauth42_session cookie\n try {\n // Get CSRF token from NextAuth\n const csrfResponse = await fetch('/api/auth/csrf');\n const { csrfToken } = await csrfResponse.json();\n\n // Call NextAuth signOut endpoint to clear session - wait for it to complete\n const signoutResponse = await fetch('/api/auth/signout', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n csrfToken,\n json: 'true',\n }),\n credentials: 'include',\n });\n\n // Log response for debugging\n console.log('[OAuth42] NextAuth signout response:', signoutResponse.status);\n } catch (err) {\n console.warn('[OAuth42] Failed to call NextAuth signout:', err);\n }\n\n // Now redirect to OAuth42 provider logout endpoint\n // This clears the oauth42_session cookie and redirects back to the app\n console.log('[OAuth42] Redirecting to backend logout:', backendLogoutUrl);\n window.location.href = backendLogoutUrl;\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","signOut"]}
|