nuxt-auth-utils 0.5.7 → 0.5.9
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 +62 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +15 -1
- package/dist/runtime/app/plugins/session.client.d.ts +1 -1
- package/dist/runtime/app/plugins/session.server.d.ts +1 -1
- package/dist/runtime/server/lib/oauth/atlassian.d.ts +66 -0
- package/dist/runtime/server/lib/oauth/atlassian.js +99 -0
- package/dist/runtime/server/lib/oauth/discord.js +3 -0
- package/dist/runtime/server/lib/oauth/facebook.js +9 -3
- package/dist/runtime/server/lib/oauth/github.js +9 -3
- package/dist/runtime/server/lib/oauth/gitlab.d.ts +5 -0
- package/dist/runtime/server/lib/oauth/gitlab.js +6 -4
- package/dist/runtime/server/lib/oauth/instagram.js +9 -3
- package/dist/runtime/server/lib/oauth/line.d.ts +45 -0
- package/dist/runtime/server/lib/oauth/line.js +75 -0
- package/dist/runtime/server/lib/webauthn/authenticate.d.ts +0 -5
- package/dist/runtime/server/lib/webauthn/register.d.ts +0 -8
- package/dist/runtime/server/plugins/oauth.d.ts +1 -1
- package/dist/runtime/server/plugins/ws.d.ts +2 -0
- package/dist/runtime/server/plugins/ws.js +7 -0
- package/dist/runtime/server/utils/session.d.ts +5 -2
- package/dist/runtime/server/utils/session.js +12 -6
- package/dist/runtime/types/oauth-config.d.ts +1 -1
- package/package.json +17 -17
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ Add Authentication to Nuxt applications with secured & sealed cookies sessions.
|
|
|
23
23
|
- [Tree-shakable server utils](#server-utils)
|
|
24
24
|
- [`<AuthState>` component](#authstate-component)
|
|
25
25
|
- [Extendable with hooks](#extend-session)
|
|
26
|
+
- [WebSocket support](#websockets-support)
|
|
26
27
|
|
|
27
28
|
It has few dependencies (only from [UnJS](https://github.com/unjs)), run on multiple JS environments (Node, Deno, Workers) and is fully typed with TypeScript.
|
|
28
29
|
|
|
@@ -204,6 +205,7 @@ It can also be set using environment variables:
|
|
|
204
205
|
|
|
205
206
|
#### Supported OAuth Providers
|
|
206
207
|
|
|
208
|
+
- Atlassian
|
|
207
209
|
- Auth0
|
|
208
210
|
- Authentik
|
|
209
211
|
- AWS Cognito
|
|
@@ -217,6 +219,7 @@ It can also be set using environment variables:
|
|
|
217
219
|
- Hubspot
|
|
218
220
|
- Instagram
|
|
219
221
|
- Keycloak
|
|
222
|
+
- Line
|
|
220
223
|
- Linear
|
|
221
224
|
- LinkedIn
|
|
222
225
|
- Microsoft
|
|
@@ -594,6 +597,65 @@ You can use the `placeholder` slot to show a placeholder on server-side and whil
|
|
|
594
597
|
|
|
595
598
|
If you are caching your routes with `routeRules`, please make sure to use [Nitro](https://github.com/unjs/nitro) >= `2.9.7` to support the client-side fetching of the user session.
|
|
596
599
|
|
|
600
|
+
## WebSockets Support
|
|
601
|
+
|
|
602
|
+
Nuxt Auth Utils is compatible with [Nitro WebSockets](https://nitro.build/guide/websocket).
|
|
603
|
+
|
|
604
|
+
Make sure to enable the `experimental.websocket` option in your `nuxt.config.ts`:
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
export default defineNuxtConfig({
|
|
608
|
+
nitro: {
|
|
609
|
+
experimental: {
|
|
610
|
+
websocket: true
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
})
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
You can use the `requireUserSession` function in the `upgrade` function to check if the user is authenticated before upgrading the WebSocket connection.
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
// server/routes/ws.ts
|
|
620
|
+
export default defineWebSocketHandler({
|
|
621
|
+
async upgrade(request) {
|
|
622
|
+
// Make sure the user is authenticated before upgrading the WebSocket connection
|
|
623
|
+
await requireUserSession(request)
|
|
624
|
+
},
|
|
625
|
+
async open(peer) {
|
|
626
|
+
const { user } = await requireUserSession(peer)
|
|
627
|
+
const username = Object.values(user).filter(Boolean).join(' ')
|
|
628
|
+
peer.send(`Hello, ${username}!`)
|
|
629
|
+
},
|
|
630
|
+
message(peer, message) {
|
|
631
|
+
peer.send(`Echo: ${message}`)
|
|
632
|
+
},
|
|
633
|
+
})
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Then, in your application, you can use the [useWebSocket](https://vueuse.org/core/useWebSocket/) composable to connect to the WebSocket:
|
|
637
|
+
|
|
638
|
+
```vue
|
|
639
|
+
<script setup>
|
|
640
|
+
const { status, data, send, open, close } = useWebSocket('/ws', { immediate: false })
|
|
641
|
+
|
|
642
|
+
// Only open the websocket after the page is hydrated (client-only)
|
|
643
|
+
onMounted(open)
|
|
644
|
+
</script>
|
|
645
|
+
|
|
646
|
+
<template>
|
|
647
|
+
<div>
|
|
648
|
+
<p>Status: {{ status }}</p>
|
|
649
|
+
<p>Data: {{ data }}</p>
|
|
650
|
+
<p>
|
|
651
|
+
<button @click="open">Open</button>
|
|
652
|
+
<button @click="close(1000, 'Closing')">Close</button>
|
|
653
|
+
<button @click="send('hello')">Send hello</button>
|
|
654
|
+
</p>
|
|
655
|
+
</div>
|
|
656
|
+
</template>
|
|
657
|
+
```
|
|
658
|
+
|
|
597
659
|
## Configuration
|
|
598
660
|
|
|
599
661
|
We leverage `runtimeConfig.session` to give the defaults option to [h3 `useSession`](https://h3.unjs.io/examples/handle-session).
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -33,6 +33,9 @@ const module = defineNuxtModule({
|
|
|
33
33
|
addPlugin(resolver.resolve("./runtime/app/plugins/session.client"));
|
|
34
34
|
addServerPlugin(resolver.resolve("./runtime/server/plugins/oauth"));
|
|
35
35
|
addServerImportsDir(resolver.resolve("./runtime/server/lib/oauth"));
|
|
36
|
+
if (nuxt.options.nitro?.experimental?.websocket) {
|
|
37
|
+
addServerPlugin(resolver.resolve("./runtime/server/plugins/ws"));
|
|
38
|
+
}
|
|
36
39
|
if (options.webAuthn) {
|
|
37
40
|
const missingDeps = [];
|
|
38
41
|
const peerDeps = ["@simplewebauthn/server", "@simplewebauthn/browser"];
|
|
@@ -102,7 +105,8 @@ const module = defineNuxtModule({
|
|
|
102
105
|
runtimeConfig.oauth.gitlab = defu(runtimeConfig.oauth.gitlab, {
|
|
103
106
|
clientId: "",
|
|
104
107
|
clientSecret: "",
|
|
105
|
-
redirectURL: ""
|
|
108
|
+
redirectURL: "",
|
|
109
|
+
baseURL: "https://gitlab.com"
|
|
106
110
|
});
|
|
107
111
|
runtimeConfig.oauth.spotify = defu(runtimeConfig.oauth.spotify, {
|
|
108
112
|
clientId: "",
|
|
@@ -260,6 +264,16 @@ const module = defineNuxtModule({
|
|
|
260
264
|
clientSecret: "",
|
|
261
265
|
redirectURL: ""
|
|
262
266
|
});
|
|
267
|
+
runtimeConfig.oauth.line = defu(runtimeConfig.oauth.line, {
|
|
268
|
+
clientId: "",
|
|
269
|
+
clientSecret: "",
|
|
270
|
+
redirectURL: ""
|
|
271
|
+
});
|
|
272
|
+
runtimeConfig.oauth.atlassian = defu(runtimeConfig.oauth.atlassian, {
|
|
273
|
+
clientId: "",
|
|
274
|
+
clientSecret: "",
|
|
275
|
+
redirectURL: ""
|
|
276
|
+
});
|
|
263
277
|
}
|
|
264
278
|
});
|
|
265
279
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
2
2
|
export default _default;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
2
2
|
export default _default;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
/**
|
|
3
|
+
* @see https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps
|
|
4
|
+
*/
|
|
5
|
+
export interface OAuthAtlassianConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Atlassian OAuth Client ID
|
|
8
|
+
* @default process.env.NUXT_OAUTH_ATLASSIAN_CLIENT_ID
|
|
9
|
+
* @see https://developer.atlassian.com/console/myapps
|
|
10
|
+
*/
|
|
11
|
+
clientId?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Atlassian OAuth Client Secret
|
|
14
|
+
* @default process.env.NUXT_OAUTH_ATLASSIAN_CLIENT_SECRET
|
|
15
|
+
* @see https://developer.atlassian.com/console/myapps
|
|
16
|
+
*/
|
|
17
|
+
clientSecret?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Redirect URL to allow overriding for situations like prod failing to determine public hostname
|
|
20
|
+
* @default process.env.NUXT_OAUTH_ATLASSIAN_REDIRECT_URL or current URL
|
|
21
|
+
* @see https://developer.atlassian.com/console/myapps
|
|
22
|
+
*/
|
|
23
|
+
redirectURL?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Atlassian OAuth Scope
|
|
26
|
+
* @default ['read:me', 'read:account']
|
|
27
|
+
* @see [Jira scopes](https://developer.atlassian.com/cloud/jira/platform/scopes-for-oauth-2-3LO-and-forge-apps) | [Confluence scopes](https://developer.atlassian.com/cloud/confluence/scopes-for-oauth-2-3LO-and-forge-apps)
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* User identity API: ['read:me', 'read:account']
|
|
31
|
+
* Confluence API: ['read:confluence-user']
|
|
32
|
+
* BRIE API: ['read:account:brie]
|
|
33
|
+
* Jira platform REST API: ['read:jira-user']
|
|
34
|
+
* Personal data reporting API: ['report:personal-data']
|
|
35
|
+
*/
|
|
36
|
+
scope?: string[];
|
|
37
|
+
/**
|
|
38
|
+
* Atlassian OAuth Audience URL
|
|
39
|
+
* @default 'https://api.atlassian.com'
|
|
40
|
+
*/
|
|
41
|
+
audienceURL?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Atlassian OAuth Authorization URL
|
|
44
|
+
* @default 'https://auth.atlassian.com/authorize'
|
|
45
|
+
*/
|
|
46
|
+
authorizationURL?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Atlassian OAuth Token URL
|
|
49
|
+
* @default 'https://auth.atlassian.com/oauth/token'
|
|
50
|
+
*/
|
|
51
|
+
tokenURL?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Require email from user, adds the ['read:me'] scope if not present
|
|
54
|
+
* @default false
|
|
55
|
+
*/
|
|
56
|
+
emailHasToBeVerified?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
59
|
+
* @default {}
|
|
60
|
+
*/
|
|
61
|
+
authorizationParams?: Record<string, string>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Atlassian User identity, Confluence, BRIE, Jira platform, Atlassian Personal data reporting
|
|
65
|
+
*/
|
|
66
|
+
export declare function defineOAuthAtlassianEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthAtlassianConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { randomUUID } from "uncrypto";
|
|
5
|
+
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
|
|
6
|
+
import { useRuntimeConfig, createError } from "#imports";
|
|
7
|
+
export function defineOAuthAtlassianEventHandler({
|
|
8
|
+
config,
|
|
9
|
+
onSuccess,
|
|
10
|
+
onError
|
|
11
|
+
}) {
|
|
12
|
+
return eventHandler(async (event) => {
|
|
13
|
+
config = defu(config, useRuntimeConfig().oauth?.atlassian, {
|
|
14
|
+
authorizationURL: "https://auth.atlassian.com/authorize",
|
|
15
|
+
tokenURL: "https://auth.atlassian.com/oauth/token",
|
|
16
|
+
audienceURL: "https://api.atlassian.com",
|
|
17
|
+
scope: ["read:me", "read:account"],
|
|
18
|
+
authorizationParams: {}
|
|
19
|
+
});
|
|
20
|
+
if (!config.clientId || !config.clientSecret) {
|
|
21
|
+
return handleMissingConfiguration(event, "atlassian", ["clientId", "clientSecret"], onError);
|
|
22
|
+
}
|
|
23
|
+
if (config.scope?.length === 0) {
|
|
24
|
+
config.scope = ["read:me"];
|
|
25
|
+
}
|
|
26
|
+
if (config.emailHasToBeVerified && !config.scope?.includes("read:me")) {
|
|
27
|
+
config.scope?.push("read:me");
|
|
28
|
+
}
|
|
29
|
+
const query = getQuery(event);
|
|
30
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
31
|
+
if (!query.code) {
|
|
32
|
+
return sendRedirect(
|
|
33
|
+
event,
|
|
34
|
+
withQuery(config.authorizationURL, {
|
|
35
|
+
audience: config.audienceURL,
|
|
36
|
+
client_id: config.clientId,
|
|
37
|
+
scope: config.scope?.join(" "),
|
|
38
|
+
redirect_uri: redirectURL,
|
|
39
|
+
state: randomUUID(),
|
|
40
|
+
response_type: "code",
|
|
41
|
+
prompt: "consent",
|
|
42
|
+
...config.authorizationParams
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (query.error) {
|
|
47
|
+
const error = createError({
|
|
48
|
+
statusCode: 401,
|
|
49
|
+
message: `Atlassian login failed: ${query.error || "Unknown error"}`,
|
|
50
|
+
data: query
|
|
51
|
+
});
|
|
52
|
+
if (!onError) throw error;
|
|
53
|
+
return onError(event, error);
|
|
54
|
+
}
|
|
55
|
+
const tokens = await requestAccessToken(config.tokenURL, {
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json"
|
|
58
|
+
},
|
|
59
|
+
body: {
|
|
60
|
+
grant_type: "authorization_code",
|
|
61
|
+
client_id: config.clientId,
|
|
62
|
+
client_secret: config.clientSecret,
|
|
63
|
+
code: query.code,
|
|
64
|
+
redirect_uri: redirectURL
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (tokens.error || !tokens.access_token) {
|
|
68
|
+
return handleAccessTokenErrorResponse(event, "atlassian", tokens, onError);
|
|
69
|
+
}
|
|
70
|
+
const user = await $fetch("https://api.atlassian.com/me", {
|
|
71
|
+
headers: {
|
|
72
|
+
"Authorization": `Bearer ${tokens.access_token}`,
|
|
73
|
+
"Content-Type": "application/json"
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (user.account_status === "inactive") {
|
|
77
|
+
const error = createError({
|
|
78
|
+
statusCode: 403,
|
|
79
|
+
statusMessage: "Atlassian account is inactive",
|
|
80
|
+
data: { accountStatus: user.account_status }
|
|
81
|
+
});
|
|
82
|
+
if (!onError) throw error;
|
|
83
|
+
return onError(event, error);
|
|
84
|
+
}
|
|
85
|
+
if (!user.email_verified) {
|
|
86
|
+
const error = createError({
|
|
87
|
+
statusCode: 400,
|
|
88
|
+
statusMessage: "Email address is not verified",
|
|
89
|
+
data: { email: user.email }
|
|
90
|
+
});
|
|
91
|
+
if (!onError) throw error;
|
|
92
|
+
return onError(event, error);
|
|
93
|
+
}
|
|
94
|
+
return onSuccess(event, {
|
|
95
|
+
user,
|
|
96
|
+
tokens
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -15,6 +15,9 @@ export function defineOAuthDiscordEventHandler({ config, onSuccess, onError }) {
|
|
|
15
15
|
if (!config.clientId || !config.clientSecret) {
|
|
16
16
|
return handleMissingConfiguration(event, "discord", ["clientId", "clientSecret"], onError);
|
|
17
17
|
}
|
|
18
|
+
if (query.error) {
|
|
19
|
+
return handleAccessTokenErrorResponse(event, "discord", query, onError);
|
|
20
|
+
}
|
|
18
21
|
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
19
22
|
if (!query.code) {
|
|
20
23
|
config.scope = config.scope || [];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect, createError } from "h3";
|
|
2
2
|
import { withQuery } from "ufo";
|
|
3
3
|
import { defu } from "defu";
|
|
4
4
|
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
|
|
5
|
-
import { useRuntimeConfig
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
6
|
export function defineOAuthFacebookEventHandler({
|
|
7
7
|
config,
|
|
8
8
|
onSuccess,
|
|
@@ -58,7 +58,13 @@ export function defineOAuthFacebookEventHandler({
|
|
|
58
58
|
`https://graph.facebook.com/v19.0/me?fields=${fields}&access_token=${accessToken}`
|
|
59
59
|
);
|
|
60
60
|
if (!user) {
|
|
61
|
-
|
|
61
|
+
const error = createError({
|
|
62
|
+
statusCode: 500,
|
|
63
|
+
message: "Could not get Facebook user",
|
|
64
|
+
data: tokens
|
|
65
|
+
});
|
|
66
|
+
if (!onError) throw error;
|
|
67
|
+
return onError(event, error);
|
|
62
68
|
}
|
|
63
69
|
return onSuccess(event, {
|
|
64
70
|
user,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect, createError } from "h3";
|
|
2
2
|
import { withQuery } from "ufo";
|
|
3
3
|
import { defu } from "defu";
|
|
4
4
|
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
|
|
5
|
-
import { useRuntimeConfig
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
6
|
export function defineOAuthGitHubEventHandler({ config, onSuccess, onError }) {
|
|
7
7
|
return eventHandler(async (event) => {
|
|
8
8
|
config = defu(config, useRuntimeConfig(event).oauth?.github, {
|
|
@@ -67,7 +67,13 @@ export function defineOAuthGitHubEventHandler({ config, onSuccess, onError }) {
|
|
|
67
67
|
});
|
|
68
68
|
const primaryEmail = emails.find((email) => email.primary);
|
|
69
69
|
if (!primaryEmail) {
|
|
70
|
-
|
|
70
|
+
const error = createError({
|
|
71
|
+
statusCode: 500,
|
|
72
|
+
message: "Could not get GitHub user email",
|
|
73
|
+
data: tokens
|
|
74
|
+
});
|
|
75
|
+
if (!onError) throw error;
|
|
76
|
+
return onError(event, error);
|
|
71
77
|
}
|
|
72
78
|
user.email = primaryEmail.email;
|
|
73
79
|
}
|
|
@@ -43,5 +43,10 @@ export interface OAuthGitLabConfig {
|
|
|
43
43
|
* @default process.env.NUXT_OAUTH_GITLAB_REDIRECT_URL
|
|
44
44
|
*/
|
|
45
45
|
redirectURL?: string;
|
|
46
|
+
/**
|
|
47
|
+
* URL of your instance, if you are self-hosting
|
|
48
|
+
* @default https://gitlab.com
|
|
49
|
+
*/
|
|
50
|
+
baseURL?: string;
|
|
46
51
|
}
|
|
47
52
|
export declare function defineOAuthGitLabEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGitLabConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -14,9 +14,11 @@ export function defineOAuthGitLabEventHandler({
|
|
|
14
14
|
onError
|
|
15
15
|
}) {
|
|
16
16
|
return eventHandler(async (event) => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const runtimeConfig = useRuntimeConfig(event).oauth?.gitlab;
|
|
18
|
+
const baseURL = config?.baseURL ?? runtimeConfig.baseURL ?? "https://gitlab.com";
|
|
19
|
+
config = defu(config, runtimeConfig, {
|
|
20
|
+
authorizationURL: `${baseURL}/oauth/authorize`,
|
|
21
|
+
tokenURL: `${baseURL}/oauth/token`,
|
|
20
22
|
authorizationParams: {}
|
|
21
23
|
});
|
|
22
24
|
const query = getQuery(event);
|
|
@@ -70,7 +72,7 @@ export function defineOAuthGitLabEventHandler({
|
|
|
70
72
|
return handleAccessTokenErrorResponse(event, "gitlab", tokens, onError);
|
|
71
73
|
}
|
|
72
74
|
const accessToken = tokens.access_token;
|
|
73
|
-
const user = await $fetch(
|
|
75
|
+
const user = await $fetch(`${baseURL}/api/v4/user`, {
|
|
74
76
|
headers: {
|
|
75
77
|
Authorization: `Bearer ${accessToken}`
|
|
76
78
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect, createError } from "h3";
|
|
2
2
|
import { withQuery } from "ufo";
|
|
3
3
|
import { defu } from "defu";
|
|
4
4
|
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
|
|
5
|
-
import { useRuntimeConfig
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
6
|
export function defineOAuthInstagramEventHandler({
|
|
7
7
|
config,
|
|
8
8
|
onSuccess,
|
|
@@ -64,7 +64,13 @@ export function defineOAuthInstagramEventHandler({
|
|
|
64
64
|
`https://graph.instagram.com/v21.0/me?fields=${fields}&access_token=${accessToken}`
|
|
65
65
|
);
|
|
66
66
|
if (!user) {
|
|
67
|
-
|
|
67
|
+
const error = createError({
|
|
68
|
+
statusCode: 500,
|
|
69
|
+
message: "Could not get Instagram user",
|
|
70
|
+
data: tokens
|
|
71
|
+
});
|
|
72
|
+
if (!onError) throw error;
|
|
73
|
+
return onError(event, error);
|
|
68
74
|
}
|
|
69
75
|
return onSuccess(event, {
|
|
70
76
|
user,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthLineConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Line OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_LINE_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Line OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_LINE_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Line OAuth Scope
|
|
15
|
+
* @default ['profile', 'openid']
|
|
16
|
+
* @see https://developers.line.biz/en/docs/line-login/integrate-line-login/
|
|
17
|
+
*/
|
|
18
|
+
scope?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Line OAuth Authorization URL
|
|
21
|
+
* @default 'https://access.line.me/oauth2/v2.1/authorize'
|
|
22
|
+
*/
|
|
23
|
+
authorizationURL?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Line OAuth Token URL
|
|
26
|
+
* @default 'https://api.line.me/oauth2/v2.1/token'
|
|
27
|
+
*/
|
|
28
|
+
tokenURL?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Line OAuth User Info URL
|
|
31
|
+
* @default 'https://api.line.me/v2/profile'
|
|
32
|
+
*/
|
|
33
|
+
userURL?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
36
|
+
* @example { bot_prompt: 'normal' }
|
|
37
|
+
*/
|
|
38
|
+
authorizationParams?: Record<string, string>;
|
|
39
|
+
/**
|
|
40
|
+
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
|
|
41
|
+
* @default process.env.NUXT_OAUTH_LINE_REDIRECT_URL or current URL
|
|
42
|
+
*/
|
|
43
|
+
redirectURL?: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function defineOAuthLineEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthLineConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect, createError } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
export function defineOAuthLineEventHandler({
|
|
7
|
+
config,
|
|
8
|
+
onSuccess,
|
|
9
|
+
onError
|
|
10
|
+
}) {
|
|
11
|
+
return eventHandler(async (event) => {
|
|
12
|
+
config = defu(config, useRuntimeConfig(event).oauth?.line, {
|
|
13
|
+
authorizationURL: "https://access.line.me/oauth2/v2.1/authorize",
|
|
14
|
+
tokenURL: "https://api.line.me/oauth2/v2.1/token",
|
|
15
|
+
userURL: "https://api.line.me/v2/profile",
|
|
16
|
+
authorizationParams: {}
|
|
17
|
+
});
|
|
18
|
+
const query = getQuery(event);
|
|
19
|
+
if (query.error) {
|
|
20
|
+
return onError(
|
|
21
|
+
event,
|
|
22
|
+
new Error(`Line login failed: ${query.error || "Unknown error"}`)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (!config.clientId || !config.clientSecret) {
|
|
26
|
+
return handleMissingConfiguration(event, "line", ["clientId", "clientSecret"], onError);
|
|
27
|
+
}
|
|
28
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
29
|
+
if (!query.code) {
|
|
30
|
+
config.scope = config.scope || ["profile", "openid"];
|
|
31
|
+
return sendRedirect(
|
|
32
|
+
event,
|
|
33
|
+
withQuery(config.authorizationURL, {
|
|
34
|
+
response_type: "code",
|
|
35
|
+
client_id: config.clientId,
|
|
36
|
+
redirect_uri: redirectURL,
|
|
37
|
+
scope: config.scope.join(" "),
|
|
38
|
+
state: query.state || "",
|
|
39
|
+
...config.authorizationParams
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const tokens = await requestAccessToken(config.tokenURL, {
|
|
44
|
+
body: {
|
|
45
|
+
grant_type: "authorization_code",
|
|
46
|
+
code: query.code,
|
|
47
|
+
client_id: config.clientId,
|
|
48
|
+
client_secret: config.clientSecret,
|
|
49
|
+
redirect_uri: redirectURL
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (tokens.error) {
|
|
53
|
+
return handleAccessTokenErrorResponse(event, "line", tokens, onError);
|
|
54
|
+
}
|
|
55
|
+
const accessToken = tokens.access_token;
|
|
56
|
+
const user = await $fetch(config.userURL, {
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${accessToken}`
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
if (!user) {
|
|
62
|
+
const error = createError({
|
|
63
|
+
statusCode: 500,
|
|
64
|
+
message: "Could not get Line user",
|
|
65
|
+
data: tokens
|
|
66
|
+
});
|
|
67
|
+
if (!onError) throw error;
|
|
68
|
+
return onError(event, error);
|
|
69
|
+
}
|
|
70
|
+
return onSuccess(event, {
|
|
71
|
+
tokens,
|
|
72
|
+
user
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { WebAuthnAuthenticateEventHandlerOptions, WebAuthnCredential } from '#auth-utils';
|
|
2
|
-
export declare function defineWebAuthnAuthenticateEventHandler<T extends WebAuthnCredential>({ storeChallenge, getChallenge, getCredential, allowCredentials, getOptions, onSuccess, onError, }: WebAuthnAuthenticateEventHandlerOptions<T>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void | import("@simplewebauthn/server").VerifiedAuthenticationResponse | {
|
|
3
|
-
requestOptions: import("@simplewebauthn/types").PublicKeyCredentialRequestOptionsJSON;
|
|
4
|
-
attemptId: string;
|
|
5
|
-
}>>;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { H3Event } from 'h3';
|
|
2
|
-
import type { WebAuthnUser, WebAuthnRegisterEventHandlerOptions } from '#auth-utils';
|
|
3
|
-
import type { ValidateUserFunction } from '~/src/runtime/types/webauthn';
|
|
4
|
-
export declare function defineWebAuthnRegisterEventHandler<T extends WebAuthnUser>({ storeChallenge, getChallenge, getOptions, validateUser, excludeCredentials, onSuccess, onError, }: WebAuthnRegisterEventHandlerOptions<T>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void | import("@simplewebauthn/server").VerifiedRegistrationResponse | {
|
|
5
|
-
creationOptions: import("@simplewebauthn/types").PublicKeyCredentialCreationOptionsJSON;
|
|
6
|
-
attemptId: string;
|
|
7
|
-
}>>;
|
|
8
|
-
export declare function validateUserData<T>(userBody: WebAuthnUser, event: H3Event, fn: ValidateUserFunction<T>): Promise<T>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("nitropack").NitroAppPlugin;
|
|
1
|
+
declare const _default: import("nitropack/types").NitroAppPlugin;
|
|
2
2
|
export default _default;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { H3Event, SessionConfig } from 'h3';
|
|
2
|
+
import { useSession } from 'h3';
|
|
2
3
|
import type { UserSession, UserSessionRequired } from '#auth-utils';
|
|
4
|
+
type UseSessionEvent = Parameters<typeof useSession>[0];
|
|
3
5
|
export interface SessionHooks {
|
|
4
6
|
/**
|
|
5
7
|
* Called when fetching the session from the API
|
|
@@ -18,7 +20,7 @@ export declare const sessionHooks: import("hookable").Hookable<SessionHooks, imp
|
|
|
18
20
|
* @param event The Request (h3) event
|
|
19
21
|
* @returns The user session
|
|
20
22
|
*/
|
|
21
|
-
export declare function getUserSession(event:
|
|
23
|
+
export declare function getUserSession(event: UseSessionEvent): Promise<UserSession>;
|
|
22
24
|
/**
|
|
23
25
|
* Set a user session
|
|
24
26
|
* @param event The Request (h3) event
|
|
@@ -46,7 +48,8 @@ export declare function clearUserSession(event: H3Event, config?: Partial<Sessio
|
|
|
46
48
|
* @param opts.message The message to use for the error (defaults to Unauthorized)
|
|
47
49
|
* @see https://github.com/atinux/nuxt-auth-utils
|
|
48
50
|
*/
|
|
49
|
-
export declare function requireUserSession(event:
|
|
51
|
+
export declare function requireUserSession(event: UseSessionEvent, opts?: {
|
|
50
52
|
statusCode?: number;
|
|
51
53
|
message?: string;
|
|
52
54
|
}): Promise<UserSessionRequired>;
|
|
55
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useSession, createError } from "h3";
|
|
1
|
+
import { useSession, createError, isEvent } from "h3";
|
|
2
2
|
import { defu } from "defu";
|
|
3
3
|
import { createHooks } from "hookable";
|
|
4
4
|
import { useRuntimeConfig } from "#imports";
|
|
@@ -26,17 +26,23 @@ export async function clearUserSession(event, config) {
|
|
|
26
26
|
export async function requireUserSession(event, opts = {}) {
|
|
27
27
|
const userSession = await getUserSession(event);
|
|
28
28
|
if (!userSession.user) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
if (isEvent(event)) {
|
|
30
|
+
throw createError({
|
|
31
|
+
statusCode: opts.statusCode || 401,
|
|
32
|
+
message: opts.message || "Unauthorized"
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
throw new Response(opts.message || "Unauthorized", {
|
|
36
|
+
status: opts.statusCode || 401
|
|
37
|
+
});
|
|
38
|
+
}
|
|
33
39
|
}
|
|
34
40
|
return userSession;
|
|
35
41
|
}
|
|
36
42
|
let sessionConfig;
|
|
37
43
|
function _useSession(event, config = {}) {
|
|
38
44
|
if (!sessionConfig) {
|
|
39
|
-
const runtimeConfig = useRuntimeConfig(event);
|
|
45
|
+
const runtimeConfig = useRuntimeConfig(isEvent(event) ? event : void 0);
|
|
40
46
|
const envSessionPassword = `${runtimeConfig.nitro?.envPrefix || "NUXT_"}SESSION_PASSWORD`;
|
|
41
47
|
sessionConfig = defu({ password: process.env[envSessionPassword] }, runtimeConfig.session);
|
|
42
48
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { H3Event, H3Error } from 'h3';
|
|
2
|
-
export type OAuthProvider = 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (string & {});
|
|
2
|
+
export type OAuthProvider = 'atlassian' | 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (string & {});
|
|
3
3
|
export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
|
|
4
4
|
export interface OAuthConfig<TConfig, TUser = any, TTokens = any> {
|
|
5
5
|
config?: TConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auth-utils",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Add Authentication to Nuxt applications with secured & sealed cookies sessions.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"type": "module",
|
|
11
|
-
"packageManager": "pnpm@9.15.
|
|
11
|
+
"packageManager": "pnpm@9.15.4",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/types.d.ts",
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@adonisjs/hash": "^9.0.5",
|
|
39
|
-
"@nuxt/kit": "^3.
|
|
39
|
+
"@nuxt/kit": "^3.15.2",
|
|
40
40
|
"defu": "^6.1.4",
|
|
41
|
-
"h3": "^1.
|
|
41
|
+
"h3": "^1.14.0",
|
|
42
42
|
"hookable": "^5.5.3",
|
|
43
43
|
"ofetch": "^1.4.1",
|
|
44
44
|
"ohash": "^1.1.4",
|
|
45
45
|
"openid-client": "^6.1.7",
|
|
46
|
-
"pathe": "^
|
|
46
|
+
"pathe": "^2.0.2",
|
|
47
47
|
"scule": "^1.3.0",
|
|
48
48
|
"uncrypto": "^0.1.3"
|
|
49
49
|
},
|
|
@@ -60,20 +60,20 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
63
|
+
"@iconify-json/simple-icons": "^1.2.21",
|
|
64
64
|
"@nuxt/devtools": "latest",
|
|
65
|
-
"@nuxt/eslint-config": "^0.7.
|
|
65
|
+
"@nuxt/eslint-config": "^0.7.5",
|
|
66
66
|
"@nuxt/module-builder": "^0.8.4",
|
|
67
|
-
"@nuxt/schema": "^3.
|
|
68
|
-
"@nuxt/test-utils": "^3.15.
|
|
69
|
-
"@nuxt/ui": "^2.
|
|
70
|
-
"@nuxt/ui-pro": "^1.
|
|
71
|
-
"@simplewebauthn/types": "^
|
|
67
|
+
"@nuxt/schema": "^3.15.2",
|
|
68
|
+
"@nuxt/test-utils": "^3.15.4",
|
|
69
|
+
"@nuxt/ui": "^2.21.0",
|
|
70
|
+
"@nuxt/ui-pro": "^1.7.0",
|
|
71
|
+
"@simplewebauthn/types": "^12.0.0",
|
|
72
72
|
"changelogen": "^0.5.7",
|
|
73
|
-
"eslint": "^9.
|
|
74
|
-
"nuxt": "^3.
|
|
75
|
-
"typescript": "
|
|
76
|
-
"vitest": "^
|
|
77
|
-
"vue-tsc": "^2.
|
|
73
|
+
"eslint": "^9.18.0",
|
|
74
|
+
"nuxt": "^3.15.2",
|
|
75
|
+
"typescript": "5.6.3",
|
|
76
|
+
"vitest": "^3.0.3",
|
|
77
|
+
"vue-tsc": "^2.2.0"
|
|
78
78
|
}
|
|
79
79
|
}
|