nuxt-auth-utils 0.5.10 → 0.5.11
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 +8 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +6 -0
- package/dist/runtime/app/composables/session.js +20 -0
- package/dist/runtime/app/plugins/session.client.js +5 -0
- package/dist/runtime/server/lib/oauth/gitea.d.ts +51 -0
- package/dist/runtime/server/lib/oauth/gitea.js +85 -0
- package/dist/runtime/server/lib/oauth/github.js +1 -0
- package/dist/runtime/server/lib/oauth/microsoft.js +3 -2
- package/dist/runtime/types/oauth-config.d.ts +1 -1
- package/dist/runtime/types/session.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ Nuxt Auth Utils automatically adds some plugins to fetch the current user sessio
|
|
|
62
62
|
|
|
63
63
|
```vue
|
|
64
64
|
<script setup>
|
|
65
|
-
const { loggedIn, user, session, fetch, clear } = useUserSession()
|
|
65
|
+
const { loggedIn, user, session, fetch, clear, openInPopup } = useUserSession()
|
|
66
66
|
</script>
|
|
67
67
|
|
|
68
68
|
<template>
|
|
@@ -74,6 +74,8 @@ const { loggedIn, user, session, fetch, clear } = useUserSession()
|
|
|
74
74
|
<div v-else>
|
|
75
75
|
<h1>Not logged in</h1>
|
|
76
76
|
<a href="/auth/github">Login with GitHub</a>
|
|
77
|
+
<!-- or open the OAuth route in a popup -->
|
|
78
|
+
<button @click="openInPopup('/auth/github')">Login with GitHub</button>
|
|
77
79
|
</div>
|
|
78
80
|
</template>
|
|
79
81
|
```
|
|
@@ -106,6 +108,10 @@ interface UserSessionComposable {
|
|
|
106
108
|
* Clear the user session and remove the session cookie.
|
|
107
109
|
*/
|
|
108
110
|
clear: () => Promise<void>
|
|
111
|
+
/**
|
|
112
|
+
* Open the OAuth route in a popup that auto-closes when successful.
|
|
113
|
+
*/
|
|
114
|
+
openInPopup: (route: string, size?: { width?: number, height?: number }) => void
|
|
109
115
|
}
|
|
110
116
|
```
|
|
111
117
|
|
|
@@ -216,6 +222,7 @@ It can also be set using environment variables:
|
|
|
216
222
|
- Facebook
|
|
217
223
|
- GitHub
|
|
218
224
|
- GitLab
|
|
225
|
+
- Gitea
|
|
219
226
|
- Google
|
|
220
227
|
- Hubspot
|
|
221
228
|
- Instagram
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -97,6 +97,12 @@ const module = defineNuxtModule({
|
|
|
97
97
|
authenticate: {}
|
|
98
98
|
});
|
|
99
99
|
runtimeConfig.oauth = defu(runtimeConfig.oauth, {});
|
|
100
|
+
runtimeConfig.oauth.gitea = defu(runtimeConfig.oauth.gitea, {
|
|
101
|
+
clientId: "",
|
|
102
|
+
clientSecret: "",
|
|
103
|
+
redirectURL: "",
|
|
104
|
+
baseURL: ""
|
|
105
|
+
});
|
|
100
106
|
runtimeConfig.oauth.github = defu(runtimeConfig.oauth.github, {
|
|
101
107
|
clientId: "",
|
|
102
108
|
clientSecret: "",
|
|
@@ -28,12 +28,32 @@ export function useUserSession() {
|
|
|
28
28
|
authReadyState.value = true;
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
+
const popupListener = (e) => {
|
|
32
|
+
if (e.key === "temp-nuxt-auth-utils-popup") {
|
|
33
|
+
fetch();
|
|
34
|
+
window.removeEventListener("storage", popupListener);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const openInPopup = (route, size = {}) => {
|
|
38
|
+
localStorage.setItem("temp-nuxt-auth-utils-popup", "true");
|
|
39
|
+
const width = size.width ?? 960;
|
|
40
|
+
const height = size.height ?? 600;
|
|
41
|
+
const top = (window.top?.outerHeight ?? 0) / 2 + (window.top?.screenY ?? 0) - height / 2;
|
|
42
|
+
const left = (window.top?.outerWidth ?? 0) / 2 + (window.top?.screenX ?? 0) - width / 2;
|
|
43
|
+
window.open(
|
|
44
|
+
route,
|
|
45
|
+
"nuxt-auth-utils-popup",
|
|
46
|
+
`width=${width}, height=${height}, top=${top}, left=${left}, toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no`
|
|
47
|
+
);
|
|
48
|
+
window.addEventListener("storage", popupListener);
|
|
49
|
+
};
|
|
31
50
|
return {
|
|
32
51
|
ready: computed(() => authReadyState.value),
|
|
33
52
|
loggedIn: computed(() => Boolean(sessionState.value.user)),
|
|
34
53
|
user: computed(() => sessionState.value.user || null),
|
|
35
54
|
session: sessionState,
|
|
36
55
|
fetch,
|
|
56
|
+
openInPopup,
|
|
37
57
|
clear
|
|
38
58
|
};
|
|
39
59
|
}
|
|
@@ -7,4 +7,9 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
|
|
7
7
|
await useUserSession().fetch();
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
|
+
if (localStorage.getItem("temp-nuxt-auth-utils-popup")) {
|
|
11
|
+
localStorage.removeItem("temp-nuxt-auth-utils-popup");
|
|
12
|
+
const error = useError();
|
|
13
|
+
if (!error.value) window.close();
|
|
14
|
+
}
|
|
10
15
|
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthGiteaConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Gitea OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_GITEA_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Gitea OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_GITEA_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Gitea OAuth Scope
|
|
15
|
+
* @default ['read:user']
|
|
16
|
+
* @see https://docs.gitea.io/en-us/oauth2-provider/
|
|
17
|
+
* @example ['read:user']
|
|
18
|
+
*/
|
|
19
|
+
scope?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Require email from user, adds the ['email'] scope if not present
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
emailRequired?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Gitea OAuth Authorization URL
|
|
27
|
+
* @default '/login/oauth/authorize'
|
|
28
|
+
*/
|
|
29
|
+
authorizationURL?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Gitea OAuth Token URL
|
|
32
|
+
* @default '/login/oauth/access_token'
|
|
33
|
+
*/
|
|
34
|
+
tokenURL?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
37
|
+
* @see https://docs.gitea.io/en-us/oauth2-provider/
|
|
38
|
+
*/
|
|
39
|
+
authorizationParams?: Record<string, string>;
|
|
40
|
+
/**
|
|
41
|
+
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
|
|
42
|
+
* @default process.env.NUXT_OAUTH_GITEA_REDIRECT_URL
|
|
43
|
+
*/
|
|
44
|
+
redirectURL?: string;
|
|
45
|
+
/**
|
|
46
|
+
* URL of your Gitea instance
|
|
47
|
+
* @default 'https://gitea.com'
|
|
48
|
+
*/
|
|
49
|
+
baseURL?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function defineOAuthGiteaEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthGiteaConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import {
|
|
5
|
+
handleMissingConfiguration,
|
|
6
|
+
handleAccessTokenErrorResponse,
|
|
7
|
+
getOAuthRedirectURL,
|
|
8
|
+
requestAccessToken
|
|
9
|
+
} from "../utils.js";
|
|
10
|
+
import { useRuntimeConfig, createError } from "#imports";
|
|
11
|
+
export function defineOAuthGiteaEventHandler({
|
|
12
|
+
config,
|
|
13
|
+
onSuccess,
|
|
14
|
+
onError
|
|
15
|
+
}) {
|
|
16
|
+
return eventHandler(async (event) => {
|
|
17
|
+
const runtimeConfig = useRuntimeConfig(event).oauth?.gitea;
|
|
18
|
+
const baseURL = config?.baseURL ?? runtimeConfig.baseURL ?? "https://gitea.com";
|
|
19
|
+
config = defu(config, runtimeConfig, {
|
|
20
|
+
authorizationURL: `${baseURL}/login/oauth/authorize`,
|
|
21
|
+
tokenURL: `${baseURL}/login/oauth/access_token`,
|
|
22
|
+
authorizationParams: {}
|
|
23
|
+
});
|
|
24
|
+
const query = getQuery(event);
|
|
25
|
+
if (query.error) {
|
|
26
|
+
const error = createError({
|
|
27
|
+
statusCode: 401,
|
|
28
|
+
message: `Gitea login failed: ${query.error || "Unknown error"}`,
|
|
29
|
+
data: query
|
|
30
|
+
});
|
|
31
|
+
if (!onError) throw error;
|
|
32
|
+
return onError(event, error);
|
|
33
|
+
}
|
|
34
|
+
if (!config.clientId || !config.clientSecret) {
|
|
35
|
+
return handleMissingConfiguration(
|
|
36
|
+
event,
|
|
37
|
+
"gitea",
|
|
38
|
+
["clientId", "clientSecret"],
|
|
39
|
+
onError
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
43
|
+
if (!query.code) {
|
|
44
|
+
config.scope = config.scope || [];
|
|
45
|
+
if (!config.scope.length) {
|
|
46
|
+
config.scope.push("read:user");
|
|
47
|
+
}
|
|
48
|
+
if (config.emailRequired && !config.scope.includes("email")) {
|
|
49
|
+
config.scope.push("email");
|
|
50
|
+
}
|
|
51
|
+
return sendRedirect(
|
|
52
|
+
event,
|
|
53
|
+
withQuery(config.authorizationURL, {
|
|
54
|
+
response_type: "code",
|
|
55
|
+
client_id: config.clientId,
|
|
56
|
+
redirect_uri: redirectURL,
|
|
57
|
+
scope: config.scope.join(" "),
|
|
58
|
+
...config.authorizationParams
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const tokens = await requestAccessToken(config.tokenURL, {
|
|
63
|
+
body: {
|
|
64
|
+
grant_type: "authorization_code",
|
|
65
|
+
client_id: config.clientId,
|
|
66
|
+
client_secret: config.clientSecret,
|
|
67
|
+
redirect_uri: redirectURL,
|
|
68
|
+
code: query.code
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
if (tokens.error) {
|
|
72
|
+
return handleAccessTokenErrorResponse(event, "gitea", tokens, onError);
|
|
73
|
+
}
|
|
74
|
+
const accessToken = tokens.access_token;
|
|
75
|
+
const user = await $fetch(`${baseURL}/api/v1/user`, {
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `token ${accessToken}`
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return onSuccess(event, {
|
|
81
|
+
user,
|
|
82
|
+
tokens
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -16,14 +16,15 @@ export function defineOAuthMicrosoftEventHandler({ config, onSuccess, onError })
|
|
|
16
16
|
const tokenURL = config.tokenURL || `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/token`;
|
|
17
17
|
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
18
18
|
if (!query.code) {
|
|
19
|
-
|
|
19
|
+
config.scope = config.scope && config.scope.length > 0 ? config.scope : ["User.Read"];
|
|
20
|
+
config.scope = [...new Set(config.scope)];
|
|
20
21
|
return sendRedirect(
|
|
21
22
|
event,
|
|
22
23
|
withQuery(authorizationURL, {
|
|
23
24
|
client_id: config.clientId,
|
|
24
25
|
response_type: "code",
|
|
25
26
|
redirect_uri: redirectURL,
|
|
26
|
-
scope: scope.join(" "),
|
|
27
|
+
scope: config.scope.join(" "),
|
|
27
28
|
...config.authorizationParams
|
|
28
29
|
})
|
|
29
30
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { H3Event, H3Error } from 'h3';
|
|
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' | 'apple' | (string & {});
|
|
2
|
+
export type OAuthProvider = 'atlassian' | 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'gitea' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | (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;
|
|
@@ -45,4 +45,11 @@ export interface UserSessionComposable {
|
|
|
45
45
|
* Clear the user session and remove the session cookie.
|
|
46
46
|
*/
|
|
47
47
|
clear: () => Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Open the OAuth route in a popup that auto-closes when successful.
|
|
50
|
+
*/
|
|
51
|
+
openInPopup: (route: string, size?: {
|
|
52
|
+
width?: number;
|
|
53
|
+
height?: number;
|
|
54
|
+
}) => void;
|
|
48
55
|
}
|