nuxt-auth-utils 0.6.1 → 0.6.2
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 +66 -3
- package/dist/module.d.mts +36 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +8 -5
- package/dist/runtime/server/lib/atproto/bluesky.js +14 -1
- package/dist/runtime/server/lib/oauth/heroku.js +1 -1
- package/dist/runtime/server/lib/oauth/hubspot.js +25 -4
- package/dist/runtime/server/lib/oauth/salesforce.js +1 -1
- package/dist/runtime/server/lib/oauth/slack.js +1 -1
- package/dist/runtime/server/lib/webauthn/authenticate.js +4 -3
- package/dist/runtime/server/lib/webauthn/register.js +5 -4
- package/dist/runtime/server/utils/atproto.js +8 -7
- package/dist/runtime/types/atproto.d.ts +6 -4
- package/dist/runtime/types/index.d.ts +1 -1
- package/dist/runtime/types/oauth-config.d.ts +14 -0
- package/dist/runtime/types/oauth-runtime-config.d.ts +119 -0
- package/dist/runtime/types/oauth-runtime-config.js +0 -0
- package/dist/runtime/utils/atproto.js +1 -2
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -410,6 +410,38 @@ export default defineNuxtConfig({
|
|
|
410
410
|
})
|
|
411
411
|
```
|
|
412
412
|
|
|
413
|
+
#### Callback path
|
|
414
|
+
|
|
415
|
+
The authorization server compares the redirect URI of the login request with the list published in the client metadata document. That document is served from `bluesky/client-metadata.json` under your app base URL (`app.baseURL`, `/` by default), so it cannot know where your login route lives.
|
|
416
|
+
|
|
417
|
+
The module fills in `/auth/<provider>`, the path its playground uses, under that base URL. The login route is served under the base URL too, so a deployment that sets `app.baseURL` publishes the path its login route answers on:
|
|
418
|
+
|
|
419
|
+
```text
|
|
420
|
+
app.baseURL: '/my-app/' -> redirect_uris: ["https://example.com/my-app/auth/bluesky"]
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Set `redirectUris` when your login route lives elsewhere; until you do, the module logs a warning in development. Both requests then use it:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
export default defineNuxtConfig({
|
|
427
|
+
runtimeConfig: {
|
|
428
|
+
oauth: {
|
|
429
|
+
bluesky: {
|
|
430
|
+
redirectUris: ['/login/bluesky']
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
})
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Declare paths only, without the origin or the app base URL. The module prepends the base URL, like it does for `/auth/<provider>`, and leaves a path that already starts with it as is. It then prepends the origin of the request, because the URL origin must match the client ID:
|
|
438
|
+
|
|
439
|
+
```text
|
|
440
|
+
app.baseURL: '/my-app/', redirectUris: ['/login/bluesky'] -> redirect_uris: ["https://example.com/my-app/login/bluesky"]
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
The `redirectUrl` option of `defineOAuthBlueskyEventHandler()` applies to the login request alone, also under the base URL. The metadata document cannot see it, so use `redirectUris` for a custom callback path.
|
|
444
|
+
|
|
413
445
|
### WebAuthn (passkey)
|
|
414
446
|
|
|
415
447
|
WebAuthn (Web Authentication) is a web standard that enhances security by replacing passwords with passkeys using public key cryptography. Users can authenticate with biometric data (like fingerprints or facial recognition) or physical devices (like USB keys), reducing the risk of phishing and password breaches. This approach offers a more secure and user-friendly authentication method, supported by major browsers and platforms.
|
|
@@ -432,6 +464,37 @@ export default defineNuxtConfig({
|
|
|
432
464
|
})
|
|
433
465
|
```
|
|
434
466
|
|
|
467
|
+
#### Relying Party ID
|
|
468
|
+
|
|
469
|
+
A passkey is bound to the domain that created it. By default, a passkey created on `example.com` cannot be used on `app.example.com`. Set `rpID` to the registrable parent domain to share passkeys between a domain and its subdomains:
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
export default defineNuxtConfig({
|
|
473
|
+
auth: {
|
|
474
|
+
webAuthn: {
|
|
475
|
+
rpID: 'example.com', // passkeys created on `example.com` also work on `app.example.com`
|
|
476
|
+
rpName: 'My App' // shown by the authenticator when creating a passkey
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
})
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Both options default to the hostname of the request. The module uses `rpID` to generate the options and to verify the response, and `rpName` only to label the passkey during registration. Both are read from `runtimeConfig`, which helps when one build serves several domains:
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
export default defineNuxtConfig({
|
|
486
|
+
runtimeConfig: {
|
|
487
|
+
webauthn: {
|
|
488
|
+
rpID: 'example.com'
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
`NUXT_WEBAUTHN_RP_ID` and `NUXT_WEBAUTHN_RP_NAME` override both at runtime.
|
|
495
|
+
|
|
496
|
+
When you define `getOptions`, the module calls it while generating the options and again while verifying the response. Return the same `rpID` in both calls, or verification fails with an `Unexpected RP ID hash` error. The expected origin is always the origin of the request, because that is what the browser sends back.
|
|
497
|
+
|
|
435
498
|
#### Example
|
|
436
499
|
|
|
437
500
|
In this example we will implement the very basic steps to register and authenticate a credential.
|
|
@@ -965,13 +1028,13 @@ Releases are automated with [uppt](https://github.com/danielroe/uppt): every pus
|
|
|
965
1028
|
|
|
966
1029
|
<!-- Badges -->
|
|
967
1030
|
[npm-version-src]: https://img.shields.io/npm/v/nuxt-auth-utils/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
968
|
-
[npm-version-href]: https://
|
|
1031
|
+
[npm-version-href]: https://npmx.dev/package/nuxt-auth-utils
|
|
969
1032
|
|
|
970
1033
|
[npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-auth-utils.svg?style=flat&colorA=020420&colorB=00DC82
|
|
971
|
-
[npm-downloads-href]: https://
|
|
1034
|
+
[npm-downloads-href]: https://npmx.dev/package-stats/nuxt-auth-utils/v/latest?granularity=monthly
|
|
972
1035
|
|
|
973
1036
|
[license-src]: https://img.shields.io/npm/l/nuxt-auth-utils.svg?style=flat&colorA=020420&colorB=00DC82
|
|
974
|
-
[license-href]: https://
|
|
1037
|
+
[license-href]: https://github.com/atinux/nuxt-auth-utils/blob/main/LICENSE
|
|
975
1038
|
|
|
976
1039
|
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
|
|
977
1040
|
[nuxt-href]: https://nuxt.com
|
package/dist/module.d.mts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import { ScryptConfig } from '@adonisjs/hash/types';
|
|
2
|
+
import { GenerateRegistrationOptionsOpts, GenerateAuthenticationOptionsOpts } from '@simplewebauthn/server';
|
|
2
3
|
import { SessionConfig } from 'nuxt/server';
|
|
4
|
+
import { OAuthRuntimeConfig } from '../dist/runtime/types/oauth-runtime-config.js';
|
|
3
5
|
|
|
6
|
+
interface WebAuthnOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Relying Party ID the passkeys are bound to.
|
|
9
|
+
*
|
|
10
|
+
* Set the registrable parent domain (such as `example.com`) to create and use passkeys across subdomains.
|
|
11
|
+
* @default the request hostname
|
|
12
|
+
*/
|
|
13
|
+
rpID?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Relying Party name shown by the authenticator when creating a passkey
|
|
16
|
+
* @default the request hostname
|
|
17
|
+
*/
|
|
18
|
+
rpName?: string;
|
|
19
|
+
}
|
|
4
20
|
interface ModuleOptions {
|
|
5
21
|
/**
|
|
6
22
|
* Enable WebAuthn (Passkeys)
|
|
7
23
|
* @default false
|
|
8
24
|
*/
|
|
9
|
-
webAuthn?: boolean;
|
|
25
|
+
webAuthn?: boolean | WebAuthnOptions;
|
|
10
26
|
/**
|
|
11
27
|
* Enable atproto OAuth (Bluesky, etc.)
|
|
12
28
|
* @default false
|
|
@@ -36,6 +52,24 @@ declare module 'nuxt/schema' {
|
|
|
36
52
|
* Session configuration, the session is sealed with a secret derived from `appSecret` (`NUXT_APP_SECRET`)
|
|
37
53
|
*/
|
|
38
54
|
session: Omit<SessionConfig, 'password'>;
|
|
55
|
+
/**
|
|
56
|
+
* WebAuthn settings, `rpID` and `rpName` are set from the `webAuthn` module option.
|
|
57
|
+
*
|
|
58
|
+
* Both can be overridden at runtime with `NUXT_WEBAUTHN_RP_ID` and `NUXT_WEBAUTHN_RP_NAME`.
|
|
59
|
+
*
|
|
60
|
+
* `register` and `authenticate` are merged into the options passed to
|
|
61
|
+
* `generateRegistrationOptions()` and `generateAuthenticationOptions()`.
|
|
62
|
+
*/
|
|
63
|
+
webauthn: {
|
|
64
|
+
rpID: string;
|
|
65
|
+
rpName: string;
|
|
66
|
+
register: Partial<GenerateRegistrationOptionsOpts>;
|
|
67
|
+
authenticate: Partial<GenerateAuthenticationOptionsOpts>;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* OAuth providers configuration, one key per provider (`github`, `google`, `keycloak`, ...).
|
|
71
|
+
*/
|
|
72
|
+
oauth: OAuthRuntimeConfig;
|
|
39
73
|
}
|
|
40
74
|
interface PublicRuntimeConfig {
|
|
41
75
|
auth: {
|
|
@@ -46,4 +80,4 @@ declare module 'nuxt/schema' {
|
|
|
46
80
|
declare const _default: any;
|
|
47
81
|
|
|
48
82
|
export { _default as default };
|
|
49
|
-
export type { ModuleOptions };
|
|
83
|
+
export type { ModuleOptions, WebAuthnOptions };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -91,7 +91,10 @@ const module$1 = defineNuxtModule({
|
|
|
91
91
|
runtimeConfig.public.auth = defu(runtimeConfig.public.auth, {
|
|
92
92
|
loadStrategy: options.loadStrategy ?? "server-first"
|
|
93
93
|
});
|
|
94
|
+
const webAuthnOptions = typeof options.webAuthn === "object" ? options.webAuthn : void 0;
|
|
94
95
|
runtimeConfig.webauthn = defu(runtimeConfig.webauthn, {
|
|
96
|
+
rpID: webAuthnOptions?.rpID ?? "",
|
|
97
|
+
rpName: webAuthnOptions?.rpName ?? "",
|
|
95
98
|
register: {},
|
|
96
99
|
authenticate: {}
|
|
97
100
|
});
|
|
@@ -145,7 +148,7 @@ const module$1 = defineNuxtModule({
|
|
|
145
148
|
clientId: "",
|
|
146
149
|
clientSecret: "",
|
|
147
150
|
connectionId: "",
|
|
148
|
-
screenHint: "",
|
|
151
|
+
screenHint: "sign-in",
|
|
149
152
|
redirectURL: ""
|
|
150
153
|
});
|
|
151
154
|
runtimeConfig.oauth.microsoft = defu(runtimeConfig.oauth.microsoft, {
|
|
@@ -360,25 +363,25 @@ const module$1 = defineNuxtModule({
|
|
|
360
363
|
redirectURL: "",
|
|
361
364
|
baseURL: "",
|
|
362
365
|
userURL: "",
|
|
363
|
-
scope:
|
|
366
|
+
scope: []
|
|
364
367
|
});
|
|
365
368
|
runtimeConfig.oauth.slack = defu(runtimeConfig.oauth.slack, {
|
|
366
369
|
clientId: "",
|
|
367
370
|
clientSecret: "",
|
|
368
371
|
redirectURL: "",
|
|
369
|
-
scope:
|
|
372
|
+
scope: []
|
|
370
373
|
});
|
|
371
374
|
runtimeConfig.oauth.heroku = defu(runtimeConfig.oauth.heroku, {
|
|
372
375
|
clientId: "",
|
|
373
376
|
clientSecret: "",
|
|
374
377
|
redirectURL: "",
|
|
375
|
-
scope:
|
|
378
|
+
scope: []
|
|
376
379
|
});
|
|
377
380
|
runtimeConfig.oauth.roblox = defu(runtimeConfig.oauth.roblox, {
|
|
378
381
|
clientId: "",
|
|
379
382
|
clientSecret: "",
|
|
380
383
|
redirectURL: "",
|
|
381
|
-
scope:
|
|
384
|
+
scope: []
|
|
382
385
|
});
|
|
383
386
|
runtimeConfig.oauth.okta = defu(runtimeConfig.oauth.okta, {
|
|
384
387
|
clientId: "",
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { defineEventHandler, createError, deleteCookie, getCookie, getQuery, sendRedirect, setCookie } from "nuxt/server";
|
|
1
|
+
import { defineEventHandler, createError, deleteCookie, getCookie, getQuery, getRequestURL, sendRedirect, setCookie, useRuntimeConfig } from "nuxt/server";
|
|
2
2
|
import { NodeOAuthClient, OAuthCallbackError, OAuthResolverError, OAuthResponseError } from "@atproto/oauth-client-node";
|
|
3
3
|
import { Agent } from "@atproto/api";
|
|
4
|
+
import { withoutBase } from "ufo";
|
|
4
5
|
import { getAtprotoClientMetadata } from "../../utils/atproto.js";
|
|
5
6
|
export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
|
|
6
7
|
return defineEventHandler(async (event) => {
|
|
7
8
|
const clientMetadata = getAtprotoClientMetadata(event, "bluesky", config);
|
|
9
|
+
if (import.meta.dev && !config?.redirectUrl) warnUnpublishedLoginPath(event, clientMetadata.redirect_uris[0]);
|
|
8
10
|
const scopes = clientMetadata.scope?.split(" ") ?? [];
|
|
9
11
|
const sessionStore = new SessionStore();
|
|
10
12
|
const stateStore = new StateStore(event);
|
|
@@ -65,6 +67,17 @@ export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
|
|
|
65
67
|
}
|
|
66
68
|
});
|
|
67
69
|
}
|
|
70
|
+
let warnedUnpublishedLoginPath = false;
|
|
71
|
+
function warnUnpublishedLoginPath(event, redirectUri) {
|
|
72
|
+
if (warnedUnpublishedLoginPath) return;
|
|
73
|
+
const runtimeConfig = useRuntimeConfig();
|
|
74
|
+
if (runtimeConfig.oauth.bluesky.redirectUris?.length) return;
|
|
75
|
+
const loginPath = getRequestURL(event).pathname;
|
|
76
|
+
const callbackPath = new URL(redirectUri).pathname;
|
|
77
|
+
if (loginPath === callbackPath) return;
|
|
78
|
+
warnedUnpublishedLoginPath = true;
|
|
79
|
+
console.warn(`[nuxt-auth-utils] The Bluesky login route is served from \`${loginPath}\`, but Bluesky redirects back to \`${callbackPath}\`. Set \`runtimeConfig.oauth.bluesky.redirectUris\` to \`['${withoutBase(loginPath, runtimeConfig.app.baseURL)}']\`.`);
|
|
80
|
+
}
|
|
68
81
|
export class StateStore {
|
|
69
82
|
constructor(event) {
|
|
70
83
|
this.event = event;
|
|
@@ -32,7 +32,7 @@ export function defineOAuthHerokuEventHandler({
|
|
|
32
32
|
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
33
33
|
const state = await handleState(event);
|
|
34
34
|
if (!query.code) {
|
|
35
|
-
config.scope = config.scope
|
|
35
|
+
config.scope = config.scope?.length ? config.scope : ["identity"];
|
|
36
36
|
return sendRedirect(
|
|
37
37
|
event,
|
|
38
38
|
withQuery(config.authorizationURL, {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { defineEventHandler, getQuery, sendRedirect, useRuntimeConfig } from "nuxt/server";
|
|
2
|
-
import { $fetch } from "ofetch";
|
|
1
|
+
import { createError, defineEventHandler, getQuery, sendRedirect, useRuntimeConfig } from "nuxt/server";
|
|
3
2
|
import { withQuery } from "ufo";
|
|
4
3
|
import defu from "defu";
|
|
5
4
|
import {
|
|
@@ -37,7 +36,7 @@ export function defineOAuthHubspotEventHandler({ config, onSuccess, onError }) {
|
|
|
37
36
|
return handleInvalidState(event, "hubspot", onError);
|
|
38
37
|
}
|
|
39
38
|
const tokens = await requestAccessToken(
|
|
40
|
-
"https://api.hubapi.com/oauth/
|
|
39
|
+
"https://api.hubapi.com/oauth/2026-09/token",
|
|
41
40
|
{
|
|
42
41
|
body: {
|
|
43
42
|
client_id: config.clientId,
|
|
@@ -51,7 +50,29 @@ export function defineOAuthHubspotEventHandler({ config, onSuccess, onError }) {
|
|
|
51
50
|
if (tokens.error) {
|
|
52
51
|
return handleAccessTokenErrorResponse(event, "hubspot", tokens, onError);
|
|
53
52
|
}
|
|
54
|
-
const info = await
|
|
53
|
+
const info = await requestAccessToken(
|
|
54
|
+
"https://api.hubapi.com/oauth/2026-09/token/introspect",
|
|
55
|
+
{
|
|
56
|
+
params: {
|
|
57
|
+
client_id: config.clientId,
|
|
58
|
+
client_secret: config.clientSecret,
|
|
59
|
+
token: tokens.access_token,
|
|
60
|
+
token_type_hint: "access_token"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
if (info.error) {
|
|
65
|
+
return handleAccessTokenErrorResponse(event, "hubspot", info, onError);
|
|
66
|
+
}
|
|
67
|
+
if (!info.active) {
|
|
68
|
+
const error = createError({
|
|
69
|
+
status: 401,
|
|
70
|
+
message: "Hubspot login failed: the access token is not active",
|
|
71
|
+
data: info
|
|
72
|
+
});
|
|
73
|
+
if (!onError) throw error;
|
|
74
|
+
return onError(event, error);
|
|
75
|
+
}
|
|
55
76
|
return onSuccess(event, {
|
|
56
77
|
user: {
|
|
57
78
|
id: info.user_id,
|
|
@@ -33,7 +33,7 @@ export function defineOAuthSalesforceEventHandler({
|
|
|
33
33
|
const pkce = await handlePkceVerifier(event);
|
|
34
34
|
const state = await handleState(event);
|
|
35
35
|
if (!query.code) {
|
|
36
|
-
config.scope = config.scope
|
|
36
|
+
config.scope = config.scope?.length ? config.scope : ["id"];
|
|
37
37
|
return sendRedirect(
|
|
38
38
|
event,
|
|
39
39
|
withQuery(config.authorizationURL, {
|
|
@@ -32,7 +32,7 @@ export function defineOAuthSlackEventHandler({
|
|
|
32
32
|
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
33
33
|
const state = await handleState(event);
|
|
34
34
|
if (!query.code) {
|
|
35
|
-
config.scope = config.scope
|
|
35
|
+
config.scope = config.scope?.length ? config.scope : ["openid", "email", "profile"];
|
|
36
36
|
return sendRedirect(
|
|
37
37
|
event,
|
|
38
38
|
withQuery(config.authorizationURL, {
|
|
@@ -15,8 +15,9 @@ export function defineWebAuthnAuthenticateEventHandler({
|
|
|
15
15
|
return defineEventHandler(async (event) => {
|
|
16
16
|
const url = getRequestURL(event, { xForwardedProto: true });
|
|
17
17
|
const body = await readBody(event);
|
|
18
|
-
const
|
|
19
|
-
|
|
18
|
+
const runtimeConfig = useRuntimeConfig();
|
|
19
|
+
const _config = defu(await getOptions?.(event, body) ?? {}, runtimeConfig.webauthn.authenticate, {
|
|
20
|
+
rpID: runtimeConfig.webauthn.rpID || url.hostname
|
|
20
21
|
});
|
|
21
22
|
if (!storeChallenge) {
|
|
22
23
|
_config.challenge = "";
|
|
@@ -47,7 +48,7 @@ export function defineWebAuthnAuthenticateEventHandler({
|
|
|
47
48
|
response: body.response,
|
|
48
49
|
expectedChallenge,
|
|
49
50
|
expectedOrigin: url.origin,
|
|
50
|
-
expectedRPID:
|
|
51
|
+
expectedRPID: _config.rpID,
|
|
51
52
|
requireUserVerification: false,
|
|
52
53
|
// TODO: make configurable https://simplewebauthn.dev/docs/advanced/passkeys#verifyauthenticationresponse
|
|
53
54
|
credential: {
|
|
@@ -24,9 +24,10 @@ export function defineWebAuthnRegisterEventHandler({
|
|
|
24
24
|
if (validateUser) {
|
|
25
25
|
user = await validateUserData(body.user, event, validateUser);
|
|
26
26
|
}
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const runtimeConfig = useRuntimeConfig();
|
|
28
|
+
const _config = defu(await getOptions?.(event, body) ?? {}, runtimeConfig.webauthn.register, {
|
|
29
|
+
rpID: runtimeConfig.webauthn.rpID || url.hostname,
|
|
30
|
+
rpName: runtimeConfig.webauthn.rpName || url.hostname,
|
|
30
31
|
userName: user.userName,
|
|
31
32
|
userDisplayName: user.displayName,
|
|
32
33
|
authenticatorSelection: {
|
|
@@ -65,7 +66,7 @@ export function defineWebAuthnRegisterEventHandler({
|
|
|
65
66
|
response: body.response,
|
|
66
67
|
expectedChallenge,
|
|
67
68
|
expectedOrigin: url.origin,
|
|
68
|
-
expectedRPID:
|
|
69
|
+
expectedRPID: _config.rpID,
|
|
69
70
|
requireUserVerification: false,
|
|
70
71
|
// TODO: make configurable https://simplewebauthn.dev/docs/advanced/passkeys#verifyregistrationresponse
|
|
71
72
|
supportedAlgorithmIDs: _config.supportedAlgorithmIDs
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { getRequestURL, useRuntimeConfig } from "nuxt/server";
|
|
2
|
-
import {
|
|
2
|
+
import { withBase } from "ufo";
|
|
3
3
|
import { getClientMetadataFilename } from "../../utils/atproto.js";
|
|
4
4
|
export function getAtprotoClientMetadata(event, provider, config) {
|
|
5
|
-
const
|
|
5
|
+
const runtimeConfig = useRuntimeConfig();
|
|
6
|
+
const providerRuntimeConfig = runtimeConfig.oauth[provider];
|
|
6
7
|
const scopes = [.../* @__PURE__ */ new Set(["atproto", ...config?.scope ?? [], ...providerRuntimeConfig.scope ?? []])];
|
|
7
8
|
const scope = scopes.join(" ");
|
|
8
9
|
const grantTypes = [.../* @__PURE__ */ new Set(["authorization_code", ...providerRuntimeConfig.grantTypes ?? []])];
|
|
9
10
|
const requestURL = getRequestURL(event, { xForwardedProto: true });
|
|
10
11
|
const baseUrl = `${requestURL.protocol}//${requestURL.host}`;
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
);
|
|
12
|
+
const withAppBase = (path) => withBase(path, runtimeConfig.app.baseURL);
|
|
13
|
+
const callbackPaths = (providerRuntimeConfig.redirectUris?.length ? providerRuntimeConfig.redirectUris : [`/auth/${provider}`]).map(withAppBase);
|
|
14
|
+
const redirectURL = new URL(baseUrl + (config?.redirectUrl ? withAppBase(config.redirectUrl) : callbackPaths[0]));
|
|
14
15
|
const dev = import.meta.dev;
|
|
15
16
|
if (dev && redirectURL.hostname === "localhost") {
|
|
16
17
|
redirectURL.hostname = "127.0.0.1";
|
|
17
18
|
}
|
|
18
|
-
const redirectUris =
|
|
19
|
-
const clientId = dev ? `http://localhost?redirect_uri=${encodeURIComponent(redirectURL.toString())}&scope=${encodeURIComponent(scope)}` : `${baseUrl}
|
|
19
|
+
const redirectUris = callbackPaths.map((uri) => new URL(`${redirectURL.protocol}//${redirectURL.host}${uri}`).toString());
|
|
20
|
+
const clientId = dev ? `http://localhost?redirect_uri=${encodeURIComponent(redirectURL.toString())}&scope=${encodeURIComponent(scope)}` : `${baseUrl}${withBase(`/${getClientMetadataFilename(provider, providerRuntimeConfig)}`, runtimeConfig.app.baseURL)}`;
|
|
20
21
|
const clientMetadata = {
|
|
21
22
|
client_name: providerRuntimeConfig.clientName || void 0,
|
|
22
23
|
client_uri: providerRuntimeConfig.clientUri || void 0,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OAuthClientMetadata } from '@atproto/oauth-client-node';
|
|
2
|
-
export
|
|
2
|
+
export type AtprotoProviderClientMetadata = {
|
|
3
3
|
/**
|
|
4
4
|
* The name of the client metadata file. This is used to construct the client ID.
|
|
5
5
|
* @example 'client-metadata.json'
|
|
@@ -46,10 +46,12 @@ export interface AtprotoProviderClientMetadata {
|
|
|
46
46
|
*/
|
|
47
47
|
responseTypes?: OAuthClientMetadata['response_types'];
|
|
48
48
|
/**
|
|
49
|
-
* At least one redirect URI is required. The URL origin must match that of the `clientId`, so declare only the path.
|
|
49
|
+
* At least one redirect URI is required. The URL origin must match that of the `clientId`, so declare only the path. Paths are resolved under the app base URL.
|
|
50
|
+
*
|
|
51
|
+
* Empty by default, in which case `/auth/<provider>` under the app base URL is used.
|
|
50
52
|
* @example ['/auth/callback']
|
|
51
53
|
*/
|
|
52
|
-
redirectUris
|
|
54
|
+
redirectUris?: `/${string}`[];
|
|
53
55
|
/**
|
|
54
56
|
* The token endpoint authentication method. `none` is the default, and the only supported value at this time.
|
|
55
57
|
* @default 'none'
|
|
@@ -59,4 +61,4 @@ export interface AtprotoProviderClientMetadata {
|
|
|
59
61
|
* DPoP is mandatory for all clients, so this must be present and true.
|
|
60
62
|
*/
|
|
61
63
|
dpopBoundAccessTokens?: OAuthClientMetadata['dpop_bound_access_tokens'];
|
|
62
|
-
}
|
|
64
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export type { User, UserSession, UserSessionRequired, UserSessionComposable, SecureSessionData } from './session.js';
|
|
2
2
|
export type { RequestEvent, SessionEvent, LegacyH3Event, WebSocketSessionEvent } from './event.js';
|
|
3
|
-
export type { OAuthConfig, OAuthProvider, ATProtoProvider, OnError } from './oauth-config.js';
|
|
3
|
+
export type { OAuthConfig, OAuthProvider, ATProtoProvider, OAuthRuntimeConfigExtension, OnError } from './oauth-config.js';
|
|
4
4
|
export type { WebAuthnCredential, WebAuthnRegisterEventHandlerOptions, WebAuthnAuthenticateEventHandlerOptions, WebAuthnComposable, WebAuthnUser, } from './webauthn.js';
|
|
@@ -2,6 +2,20 @@ import type { NuxtError, RequestEvent } from 'nuxt/server';
|
|
|
2
2
|
export type ATProtoProvider = 'bluesky';
|
|
3
3
|
export type OAuthProvider = ATProtoProvider | 'atlassian' | 'auth0' | 'authentik' | 'azureb2c' | 'battledotnet' | 'cloudflare' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'gitea' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'kick' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'mastodon' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'wechat' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | 'livechat' | 'salesforce' | 'slack' | 'heroku' | 'roblox' | 'okta' | 'ory' | 'shopifyCustomer' | 'oidc' | 'osu' | 'riotgames' | 'box' | 'zendesk' | (string & {});
|
|
4
4
|
export type OnError = (event: RequestEvent, error: NuxtError) => unknown;
|
|
5
|
+
/**
|
|
6
|
+
* Augment this interface to type the runtime configuration of OAuth providers that are not
|
|
7
|
+
* shipped with the module, such as providers added by another module.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* declare module '#auth-utils' {
|
|
11
|
+
* interface OAuthRuntimeConfigExtension {
|
|
12
|
+
* myProvider: { clientId: string, clientSecret: string }
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export interface OAuthRuntimeConfigExtension {
|
|
18
|
+
}
|
|
5
19
|
export interface OAuthConfig<TConfig, TResult = {
|
|
6
20
|
user: any;
|
|
7
21
|
tokens: any;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { AtprotoProviderClientMetadata } from './atproto.js';
|
|
2
|
+
import type { OAuthRuntimeConfigExtension } from './oauth-config.js';
|
|
3
|
+
import type { LiveChatConfig } from '../server/lib/oauth/livechat.js';
|
|
4
|
+
import type { OAuthAppleConfig } from '../server/lib/oauth/apple.js';
|
|
5
|
+
import type { OAuthAtlassianConfig } from '../server/lib/oauth/atlassian.js';
|
|
6
|
+
import type { OAuthAuth0Config } from '../server/lib/oauth/auth0.js';
|
|
7
|
+
import type { OAuthAuthentikConfig } from '../server/lib/oauth/authentik.js';
|
|
8
|
+
import type { OAuthAzureB2CConfig } from '../server/lib/oauth/azureb2c.js';
|
|
9
|
+
import type { OAuthBattledotnetConfig } from '../server/lib/oauth/battledotnet.js';
|
|
10
|
+
import type { OAuthBoxConfig } from '../server/lib/oauth/box.js';
|
|
11
|
+
import type { OAuthCloudflareConfig } from '../server/lib/oauth/cloudflare.js';
|
|
12
|
+
import type { OAuthCognitoConfig } from '../server/lib/oauth/cognito.js';
|
|
13
|
+
import type { OAuthDiscordConfig } from '../server/lib/oauth/discord.js';
|
|
14
|
+
import type { OAuthDropboxConfig } from '../server/lib/oauth/dropbox.js';
|
|
15
|
+
import type { OAuthFacebookConfig } from '../server/lib/oauth/facebook.js';
|
|
16
|
+
import type { OAuthGitHubConfig } from '../server/lib/oauth/github.js';
|
|
17
|
+
import type { OAuthGitLabConfig } from '../server/lib/oauth/gitlab.js';
|
|
18
|
+
import type { OAuthGiteaConfig } from '../server/lib/oauth/gitea.js';
|
|
19
|
+
import type { OAuthGoogleConfig } from '../server/lib/oauth/google.js';
|
|
20
|
+
import type { OAuthHerokuConfig } from '../server/lib/oauth/heroku.js';
|
|
21
|
+
import type { OAuthHubspotConfig } from '../server/lib/oauth/hubspot.js';
|
|
22
|
+
import type { OAuthInstagramConfig } from '../server/lib/oauth/instagram.js';
|
|
23
|
+
import type { OAuthKeycloakConfig } from '../server/lib/oauth/keycloak.js';
|
|
24
|
+
import type { OAuthKickConfig } from '../server/lib/oauth/kick.js';
|
|
25
|
+
import type { OAuthLineConfig } from '../server/lib/oauth/line.js';
|
|
26
|
+
import type { OAuthLinearConfig } from '../server/lib/oauth/linear.js';
|
|
27
|
+
import type { OAuthLinkedInConfig } from '../server/lib/oauth/linkedin.js';
|
|
28
|
+
import type { OAuthMastodonConfig } from '../server/lib/oauth/mastodon.js';
|
|
29
|
+
import type { OAuthMicrosoftConfig } from '../server/lib/oauth/microsoft.js';
|
|
30
|
+
import type { OAuthOidcConfig } from '../server/lib/oauth/oidc.js';
|
|
31
|
+
import type { OAuthOktaConfig } from '../server/lib/oauth/okta.js';
|
|
32
|
+
import type { OAuthOryConfig } from '../server/lib/oauth/ory.js';
|
|
33
|
+
import type { OAuthOsuConfig } from '../server/lib/oauth/osu.js';
|
|
34
|
+
import type { OAuthPaypalConfig } from '../server/lib/oauth/paypal.js';
|
|
35
|
+
import type { OAuthPolarConfig } from '../server/lib/oauth/polar.js';
|
|
36
|
+
import type { OAuthRiotGamesConfig } from '../server/lib/oauth/riotgames.js';
|
|
37
|
+
import type { OAuthRobloxConfig } from '../server/lib/oauth/roblox.js';
|
|
38
|
+
import type { OAuthSalesforceConfig } from '../server/lib/oauth/salesforce.js';
|
|
39
|
+
import type { OAuthSeznamConfig } from '../server/lib/oauth/seznam.js';
|
|
40
|
+
import type { OAuthShopifyCustomerConfig } from '../server/lib/oauth/shopifyCustomer.js';
|
|
41
|
+
import type { OAuthSlackConfig } from '../server/lib/oauth/slack.js';
|
|
42
|
+
import type { OAuthSpotifyConfig } from '../server/lib/oauth/spotify.js';
|
|
43
|
+
import type { OAuthSteamConfig } from '../server/lib/oauth/steam.js';
|
|
44
|
+
import type { OAuthStravaConfig } from '../server/lib/oauth/strava.js';
|
|
45
|
+
import type { OAuthTikTokConfig } from '../server/lib/oauth/tiktok.js';
|
|
46
|
+
import type { OAuthTwitchConfig } from '../server/lib/oauth/twitch.js';
|
|
47
|
+
import type { OAuthVKConfig } from '../server/lib/oauth/vk.js';
|
|
48
|
+
import type { OAuthWechatConfig } from '../server/lib/oauth/wechat.js';
|
|
49
|
+
import type { OAuthWorkOSConfig } from '../server/lib/oauth/workos.js';
|
|
50
|
+
import type { OAuthXConfig } from '../server/lib/oauth/x.js';
|
|
51
|
+
import type { OAuthXSUAAConfig } from '../server/lib/oauth/xsuaa.js';
|
|
52
|
+
import type { OAuthYandexConfig } from '../server/lib/oauth/yandex.js';
|
|
53
|
+
import type { OAuthZendeskConfig } from '../server/lib/oauth/zendesk.js';
|
|
54
|
+
import type { OAuthZitadelConfig } from '../server/lib/oauth/zitadel.js';
|
|
55
|
+
/**
|
|
56
|
+
* Runtime configuration of every OAuth provider supported by the module, exposed as `runtimeConfig.oauth`.
|
|
57
|
+
*
|
|
58
|
+
* Keys are the provider names accepted by `defineOAuth<Provider>EventHandler()`, and they are always
|
|
59
|
+
* set by the module, so server code can read them without checking for `undefined`. In `nuxt.config`,
|
|
60
|
+
* `runtimeConfig` is deep-partial, so you only write the providers you use.
|
|
61
|
+
* Providers contributed by other modules can add themselves through the `OAuthRuntimeConfigExtension`
|
|
62
|
+
* interface, and any other key stays `unknown`.
|
|
63
|
+
*/
|
|
64
|
+
export interface OAuthRuntimeConfig extends OAuthRuntimeConfigExtension {
|
|
65
|
+
[provider: string]: unknown;
|
|
66
|
+
apple: OAuthAppleConfig;
|
|
67
|
+
atlassian: OAuthAtlassianConfig;
|
|
68
|
+
auth0: OAuthAuth0Config;
|
|
69
|
+
authentik: OAuthAuthentikConfig;
|
|
70
|
+
azureb2c: OAuthAzureB2CConfig;
|
|
71
|
+
battledotnet: OAuthBattledotnetConfig;
|
|
72
|
+
bluesky: AtprotoProviderClientMetadata;
|
|
73
|
+
box: OAuthBoxConfig;
|
|
74
|
+
cloudflare: OAuthCloudflareConfig;
|
|
75
|
+
cognito: OAuthCognitoConfig;
|
|
76
|
+
discord: OAuthDiscordConfig;
|
|
77
|
+
dropbox: OAuthDropboxConfig;
|
|
78
|
+
facebook: OAuthFacebookConfig;
|
|
79
|
+
gitea: OAuthGiteaConfig;
|
|
80
|
+
github: OAuthGitHubConfig;
|
|
81
|
+
gitlab: OAuthGitLabConfig;
|
|
82
|
+
google: OAuthGoogleConfig;
|
|
83
|
+
heroku: OAuthHerokuConfig;
|
|
84
|
+
hubspot: OAuthHubspotConfig;
|
|
85
|
+
instagram: OAuthInstagramConfig;
|
|
86
|
+
keycloak: OAuthKeycloakConfig;
|
|
87
|
+
kick: OAuthKickConfig;
|
|
88
|
+
line: OAuthLineConfig;
|
|
89
|
+
linear: OAuthLinearConfig;
|
|
90
|
+
linkedin: OAuthLinkedInConfig;
|
|
91
|
+
livechat: LiveChatConfig;
|
|
92
|
+
mastodon: OAuthMastodonConfig;
|
|
93
|
+
microsoft: OAuthMicrosoftConfig;
|
|
94
|
+
oidc: OAuthOidcConfig;
|
|
95
|
+
okta: OAuthOktaConfig;
|
|
96
|
+
ory: OAuthOryConfig;
|
|
97
|
+
osu: OAuthOsuConfig;
|
|
98
|
+
paypal: OAuthPaypalConfig;
|
|
99
|
+
polar: OAuthPolarConfig;
|
|
100
|
+
riotgames: OAuthRiotGamesConfig;
|
|
101
|
+
roblox: OAuthRobloxConfig;
|
|
102
|
+
salesforce: OAuthSalesforceConfig;
|
|
103
|
+
seznam: OAuthSeznamConfig;
|
|
104
|
+
shopifyCustomer: OAuthShopifyCustomerConfig;
|
|
105
|
+
slack: OAuthSlackConfig;
|
|
106
|
+
spotify: OAuthSpotifyConfig;
|
|
107
|
+
steam: OAuthSteamConfig;
|
|
108
|
+
strava: OAuthStravaConfig;
|
|
109
|
+
tiktok: OAuthTikTokConfig;
|
|
110
|
+
twitch: OAuthTwitchConfig;
|
|
111
|
+
vk: OAuthVKConfig;
|
|
112
|
+
wechat: OAuthWechatConfig;
|
|
113
|
+
workos: OAuthWorkOSConfig;
|
|
114
|
+
x: OAuthXConfig;
|
|
115
|
+
xsuaa: OAuthXSUAAConfig;
|
|
116
|
+
yandex: OAuthYandexConfig;
|
|
117
|
+
zendesk: OAuthZendeskConfig;
|
|
118
|
+
zitadel: OAuthZitadelConfig;
|
|
119
|
+
}
|
|
File without changes
|
|
@@ -10,8 +10,7 @@ export const atprotoProviderDefaultClientMetadata = {
|
|
|
10
10
|
grantTypes: ["authorization_code"],
|
|
11
11
|
responseTypes: ["code"],
|
|
12
12
|
applicationType: "web",
|
|
13
|
-
|
|
14
|
-
redirectUris: void 0,
|
|
13
|
+
redirectUris: [],
|
|
15
14
|
dpopBoundAccessTokens: true,
|
|
16
15
|
tokenEndpointAuthMethod: "none"
|
|
17
16
|
};
|
package/dist/types.d.mts
CHANGED