@thunderid/nuxt 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +13 -13
- package/dist/runtime/components/ThunderIDRoot.js +1 -1
- package/dist/runtime/components/actions/SignInButton.js +4 -4
- package/dist/runtime/components/actions/SignOutButton.js +3 -3
- package/dist/runtime/components/actions/SignUpButton.js +4 -4
- package/dist/runtime/components/auth/Callback.js +1 -1
- package/dist/runtime/components/auth/SignIn.js +1 -1
- package/dist/runtime/components/auth/SignUp.js +6 -5
- package/dist/runtime/components/control/Loading.js +2 -2
- package/dist/runtime/components/control/SignedIn.js +2 -2
- package/dist/runtime/components/control/SignedOut.js +2 -2
- package/dist/runtime/components/organization/Organization.js +2 -2
- package/dist/runtime/components/user/User.js +2 -2
- package/dist/runtime/composables/useThunderID.js +2 -2
- package/dist/runtime/middleware/defineThunderIDMiddleware.js +2 -2
- package/dist/runtime/plugins/thunderid.js +4 -3
- package/dist/runtime/server/ThunderIDNuxtClient.js +9 -11
- package/dist/runtime/server/plugins/thunderid-ssr.js +5 -5
- package/dist/runtime/server/routes/auth/branding/branding.get.js +1 -1
- package/dist/runtime/server/routes/auth/session/callback.get.js +5 -5
- package/dist/runtime/server/routes/auth/session/signin.get.js +1 -1
- package/dist/runtime/server/utils/session.d.ts +2 -2
- package/dist/runtime/server/utils/session.js +9 -9
- package/dist/runtime/types.d.ts +2 -2
- package/dist/runtime/utils/log.js +1 -1
- package/dist/runtime/utils/url-validation.js +1 -1
- package/package.json +11 -8
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
-
Nuxt SDK for ThunderID. Provides authentication for Nuxt 3 applications with support for SSR, server routes, and
|
|
3
|
+
Nuxt SDK for ThunderID. Provides authentication for Nuxt 3 applications with support for SSR, server routes, and
|
|
4
|
+
composables.
|
|
4
5
|
|
|
5
6
|
## Installation
|
|
6
7
|
|
package/dist/module.d.mts
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -13,13 +13,13 @@ const module$1 = defineNuxtModule({
|
|
|
13
13
|
const publicConfig = defu(
|
|
14
14
|
// Layer 1: environment variables — only win when actually set
|
|
15
15
|
{
|
|
16
|
-
afterSignInUrl: process.env
|
|
17
|
-
afterSignOutUrl: process.env
|
|
18
|
-
applicationId: process.env
|
|
19
|
-
baseUrl: process.env
|
|
20
|
-
clientId: process.env
|
|
21
|
-
signInUrl: process.env
|
|
22
|
-
signUpUrl: process.env
|
|
16
|
+
afterSignInUrl: process.env.NUXT_PUBLIC_THUNDERID_AFTER_SIGN_IN_URL,
|
|
17
|
+
afterSignOutUrl: process.env.NUXT_PUBLIC_THUNDERID_AFTER_SIGN_OUT_URL,
|
|
18
|
+
applicationId: process.env.NUXT_PUBLIC_THUNDERID_APPLICATION_ID,
|
|
19
|
+
baseUrl: process.env.NUXT_PUBLIC_THUNDERID_BASE_URL,
|
|
20
|
+
clientId: process.env.NUXT_PUBLIC_THUNDERID_CLIENT_ID,
|
|
21
|
+
signInUrl: process.env.NUXT_PUBLIC_THUNDERID_SIGN_IN_URL,
|
|
22
|
+
signUpUrl: process.env.NUXT_PUBLIC_THUNDERID_SIGN_UP_URL
|
|
23
23
|
},
|
|
24
24
|
// Layer 2: nuxt.config.ts options
|
|
25
25
|
userOptions,
|
|
@@ -31,8 +31,8 @@ const module$1 = defineNuxtModule({
|
|
|
31
31
|
}
|
|
32
32
|
);
|
|
33
33
|
const privateConfig = {
|
|
34
|
-
clientSecret: process.env
|
|
35
|
-
sessionSecret: process.env
|
|
34
|
+
clientSecret: process.env.THUNDERID_CLIENT_SECRET || userOptions.clientSecret || "",
|
|
35
|
+
sessionSecret: process.env.THUNDERID_SESSION_SECRET || userOptions.sessionSecret || ""
|
|
36
36
|
};
|
|
37
37
|
const { options } = nuxt;
|
|
38
38
|
options.runtimeConfig.thunderid = defu(
|
|
@@ -56,14 +56,14 @@ const module$1 = defineNuxtModule({
|
|
|
56
56
|
}
|
|
57
57
|
);
|
|
58
58
|
const publicThunderID = options.runtimeConfig.public.thunderid;
|
|
59
|
-
if (publicThunderID?.
|
|
60
|
-
delete publicThunderID
|
|
59
|
+
if (publicThunderID?.clientSecret) {
|
|
60
|
+
delete publicThunderID.clientSecret;
|
|
61
61
|
console.error(
|
|
62
62
|
`[${PACKAGE_NAME}] SECURITY: clientSecret found in public config. Removed. Use THUNDERID_CLIENT_SECRET env var.`
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
|
-
if (publicThunderID?.
|
|
66
|
-
delete publicThunderID
|
|
65
|
+
if (publicThunderID?.sessionSecret) {
|
|
66
|
+
delete publicThunderID.sessionSecret;
|
|
67
67
|
console.error(
|
|
68
68
|
`[${PACKAGE_NAME}] SECURITY: sessionSecret found in public config. Removed. Use THUNDERID_SESSION_SECRET env var.`
|
|
69
69
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { navigateTo } from "#app";
|
|
1
2
|
import { ThunderIDRuntimeError } from "@thunderid/browser";
|
|
2
3
|
import { BaseSignInButton } from "@thunderid/vue";
|
|
3
4
|
import { defineComponent, h, ref } from "vue";
|
|
4
|
-
import { navigateTo } from "#app";
|
|
5
5
|
import { useThunderID } from "#imports";
|
|
6
6
|
const SignInButton = defineComponent({
|
|
7
7
|
emits: ["click", "error"],
|
|
@@ -34,14 +34,14 @@ const SignInButton = defineComponent({
|
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
return () => {
|
|
37
|
-
const slotContent = slots
|
|
37
|
+
const slotContent = slots.default ? () => slots.default({ isLoading: isLoading.value }) : void 0;
|
|
38
38
|
return h(
|
|
39
39
|
BaseSignInButton,
|
|
40
40
|
{
|
|
41
|
-
class: attrs
|
|
41
|
+
class: attrs.class,
|
|
42
42
|
isLoading: isLoading.value,
|
|
43
43
|
onClick: handleSignIn,
|
|
44
|
-
style: attrs
|
|
44
|
+
style: attrs.style
|
|
45
45
|
},
|
|
46
46
|
slotContent
|
|
47
47
|
);
|
|
@@ -26,14 +26,14 @@ const SignOutButton = defineComponent({
|
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
return () => {
|
|
29
|
-
const slotContent = slots
|
|
29
|
+
const slotContent = slots.default ? () => slots.default({ isLoading: isLoading.value }) : void 0;
|
|
30
30
|
return h(
|
|
31
31
|
BaseSignOutButton,
|
|
32
32
|
{
|
|
33
|
-
class: attrs
|
|
33
|
+
class: attrs.class,
|
|
34
34
|
isLoading: isLoading.value,
|
|
35
35
|
onClick: handleSignOut,
|
|
36
|
-
style: attrs
|
|
36
|
+
style: attrs.style
|
|
37
37
|
},
|
|
38
38
|
slotContent
|
|
39
39
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { navigateTo } from "#app";
|
|
1
2
|
import { ThunderIDRuntimeError } from "@thunderid/browser";
|
|
2
3
|
import { BaseSignUpButton } from "@thunderid/vue";
|
|
3
4
|
import { defineComponent, h, ref } from "vue";
|
|
4
|
-
import { navigateTo } from "#app";
|
|
5
5
|
import { useThunderID } from "#imports";
|
|
6
6
|
const SignUpButton = defineComponent({
|
|
7
7
|
emits: ["click", "error"],
|
|
@@ -31,14 +31,14 @@ const SignUpButton = defineComponent({
|
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
33
|
return () => {
|
|
34
|
-
const slotContent = slots
|
|
34
|
+
const slotContent = slots.default ? () => slots.default({ isLoading: isLoading.value }) : void 0;
|
|
35
35
|
return h(
|
|
36
36
|
BaseSignUpButton,
|
|
37
37
|
{
|
|
38
|
-
class: attrs
|
|
38
|
+
class: attrs.class,
|
|
39
39
|
isLoading: isLoading.value,
|
|
40
40
|
onClick: handleSignUp,
|
|
41
|
-
style: attrs
|
|
41
|
+
style: attrs.style
|
|
42
42
|
},
|
|
43
43
|
slotContent
|
|
44
44
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { navigateTo } from "#app";
|
|
1
2
|
import { BaseSignIn } from "@thunderid/vue";
|
|
2
3
|
import { defineComponent, h } from "vue";
|
|
3
|
-
import { navigateTo } from "#app";
|
|
4
4
|
import { useThunderID } from "#imports";
|
|
5
5
|
const SignIn = defineComponent({
|
|
6
6
|
emits: ["error", "success"],
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { navigateTo } from "#app";
|
|
1
2
|
import {
|
|
2
3
|
EmbeddedFlowResponseType,
|
|
3
4
|
EmbeddedFlowType
|
|
4
5
|
} from "@thunderid/browser";
|
|
5
6
|
import { BaseSignUp } from "@thunderid/vue";
|
|
6
7
|
import { defineComponent, h } from "vue";
|
|
7
|
-
import { navigateTo } from "#app";
|
|
8
8
|
import { useThunderID } from "#imports";
|
|
9
9
|
const SignUp = defineComponent({
|
|
10
10
|
name: "SignUp",
|
|
@@ -24,7 +24,7 @@ const SignUp = defineComponent({
|
|
|
24
24
|
variant: { default: "outlined", type: String }
|
|
25
25
|
},
|
|
26
26
|
setup(props, { slots }) {
|
|
27
|
-
const { signUp, isInitialized, applicationId } = useThunderID();
|
|
27
|
+
const { signUp, isInitialized, applicationId, scopes } = useThunderID();
|
|
28
28
|
const handleInitialize = async (payload) => {
|
|
29
29
|
let applicationIdFromUrl = null;
|
|
30
30
|
if (import.meta.client) {
|
|
@@ -34,7 +34,8 @@ const SignUp = defineComponent({
|
|
|
34
34
|
const effectiveApplicationId = applicationId || applicationIdFromUrl || void 0;
|
|
35
35
|
const initialPayload = payload || {
|
|
36
36
|
flowType: EmbeddedFlowType.Registration,
|
|
37
|
-
...effectiveApplicationId && { applicationId: effectiveApplicationId }
|
|
37
|
+
...effectiveApplicationId && { applicationId: effectiveApplicationId },
|
|
38
|
+
...scopes && { scopes }
|
|
38
39
|
};
|
|
39
40
|
return await signUp(initialPayload);
|
|
40
41
|
};
|
|
@@ -46,7 +47,7 @@ const SignUp = defineComponent({
|
|
|
46
47
|
await navigateTo(oauthRedirectUrl, { external: true });
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
|
-
if (props.shouldRedirectAfterSignUp && response?.type !== EmbeddedFlowResponseType.Redirection && props.afterSignUpUrl) {
|
|
50
|
+
if (props.shouldRedirectAfterSignUp && response?.type !== EmbeddedFlowResponseType.Redirection && props.afterSignUpUrl && !response?.assertion) {
|
|
50
51
|
await navigateTo(props.afterSignUpUrl, { external: true });
|
|
51
52
|
}
|
|
52
53
|
if (props.shouldRedirectAfterSignUp && response?.type === EmbeddedFlowResponseType.Redirection && response?.data?.redirectURL && !response.data.redirectURL.includes("oauth") && !response.data.redirectURL.includes("auth")) {
|
|
@@ -72,7 +73,7 @@ const SignUp = defineComponent({
|
|
|
72
73
|
size: props.size,
|
|
73
74
|
variant: props.variant
|
|
74
75
|
},
|
|
75
|
-
slots
|
|
76
|
+
slots.default ? { default: (renderProps) => slots.default(renderProps) } : void 0
|
|
76
77
|
);
|
|
77
78
|
}
|
|
78
79
|
});
|
|
@@ -6,10 +6,10 @@ const Loading = defineComponent({
|
|
|
6
6
|
const { isLoading } = useThunderID();
|
|
7
7
|
return () => {
|
|
8
8
|
if (!isLoading.value) {
|
|
9
|
-
const fallback = slots
|
|
9
|
+
const fallback = slots.fallback?.();
|
|
10
10
|
return fallback ? h(Fragment, {}, fallback) : null;
|
|
11
11
|
}
|
|
12
|
-
const content = slots
|
|
12
|
+
const content = slots.default?.();
|
|
13
13
|
return content ? h(Fragment, {}, content) : null;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -6,10 +6,10 @@ const SignedIn = defineComponent({
|
|
|
6
6
|
const { isSignedIn } = useThunderID();
|
|
7
7
|
return () => {
|
|
8
8
|
if (!isSignedIn.value) {
|
|
9
|
-
const fallback = slots
|
|
9
|
+
const fallback = slots.fallback?.();
|
|
10
10
|
return fallback ? h(Fragment, {}, fallback) : null;
|
|
11
11
|
}
|
|
12
|
-
const content = slots
|
|
12
|
+
const content = slots.default?.();
|
|
13
13
|
return content ? h(Fragment, {}, content) : null;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -6,10 +6,10 @@ const SignedOut = defineComponent({
|
|
|
6
6
|
const { isSignedIn } = useThunderID();
|
|
7
7
|
return () => {
|
|
8
8
|
if (isSignedIn.value) {
|
|
9
|
-
const fallback = slots
|
|
9
|
+
const fallback = slots.fallback?.();
|
|
10
10
|
return fallback ? h(Fragment, {}, fallback) : null;
|
|
11
11
|
}
|
|
12
|
-
const content = slots
|
|
12
|
+
const content = slots.default?.();
|
|
13
13
|
return content ? h(Fragment, {}, content) : null;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -6,10 +6,10 @@ const Organization = defineComponent({
|
|
|
6
6
|
const { currentOrganization } = useOrganization();
|
|
7
7
|
return () => {
|
|
8
8
|
if (!currentOrganization?.value) {
|
|
9
|
-
const fallback = slots
|
|
9
|
+
const fallback = slots.fallback?.();
|
|
10
10
|
return fallback ? h(Fragment, {}, fallback) : null;
|
|
11
11
|
}
|
|
12
|
-
const content = slots
|
|
12
|
+
const content = slots.default?.({ organization: currentOrganization.value });
|
|
13
13
|
return content ? h(Fragment, {}, content) : null;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -6,10 +6,10 @@ const User = defineComponent({
|
|
|
6
6
|
const { user } = useThunderID();
|
|
7
7
|
return () => {
|
|
8
8
|
if (!user.value) {
|
|
9
|
-
const fallback = slots
|
|
9
|
+
const fallback = slots.fallback?.();
|
|
10
10
|
return fallback ? h(Fragment, {}, fallback) : null;
|
|
11
11
|
}
|
|
12
|
-
const content = slots
|
|
12
|
+
const content = slots.default?.({ user: user.value });
|
|
13
13
|
return content ? h(Fragment, {}, content) : null;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { navigateTo, useState, useRuntimeConfig } from "#app";
|
|
1
2
|
import { EmbeddedSignInFlowStatus, getRedirectBasedSignUpUrl } from "@thunderid/browser";
|
|
2
3
|
import { useThunderID as useThunderIDVue } from "@thunderid/vue";
|
|
3
|
-
import { navigateTo, useState, useRuntimeConfig } from "#app";
|
|
4
4
|
export function useThunderID() {
|
|
5
5
|
const context = useThunderIDVue();
|
|
6
6
|
const signIn = async (...args) => {
|
|
@@ -30,7 +30,7 @@ export function useThunderID() {
|
|
|
30
30
|
return res.data;
|
|
31
31
|
}
|
|
32
32
|
const options = arg0;
|
|
33
|
-
const returnTo = typeof options?.
|
|
33
|
+
const returnTo = typeof options?.returnTo === "string" ? options.returnTo : void 0;
|
|
34
34
|
const url = returnTo ? `/api/auth/signin?returnTo=${encodeURIComponent(returnTo)}` : "/api/auth/signin";
|
|
35
35
|
await navigateTo(url, { external: true });
|
|
36
36
|
return void 0;
|
|
@@ -9,11 +9,11 @@ export function defineThunderIDMiddleware(options = {}) {
|
|
|
9
9
|
return navigateTo(`${redirectTo}?returnTo=${returnTo}`, { external: true });
|
|
10
10
|
}
|
|
11
11
|
const user = authState.value.user;
|
|
12
|
-
if (requireOrganization && !user?.
|
|
12
|
+
if (requireOrganization && !user?.organizationId) {
|
|
13
13
|
return navigateTo(redirectTo, { external: true });
|
|
14
14
|
}
|
|
15
15
|
if (requireScopes.length > 0) {
|
|
16
|
-
const sessionScopes = String(user?.
|
|
16
|
+
const sessionScopes = String(user?.scopes ?? "").split(" ");
|
|
17
17
|
const hasAllScopes = requireScopes.every((s) => sessionScopes.includes(s));
|
|
18
18
|
if (!hasAllScopes) {
|
|
19
19
|
return navigateTo(redirectTo, { external: true });
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useState, useRequestEvent, useRuntimeConfig, navigateTo } from "#app";
|
|
1
2
|
import { getRedirectBasedSignUpUrl } from "@thunderid/browser";
|
|
2
3
|
import { ThunderIDPlugin, THUNDERID_KEY } from "@thunderid/vue";
|
|
3
4
|
import { computed } from "vue";
|
|
4
5
|
import ThunderIDRoot from "../components/ThunderIDRoot.js";
|
|
5
|
-
import { defineNuxtPlugin, useState, useRequestEvent, useRuntimeConfig, navigateTo } from "#app";
|
|
6
6
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
7
7
|
const publicConfig = useRuntimeConfig().public.thunderid;
|
|
8
8
|
if (import.meta.client && import.meta.dev) {
|
|
@@ -46,7 +46,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
46
46
|
user: ssrContext.session?.sub ? { sub: ssrContext.session.sub } : null
|
|
47
47
|
};
|
|
48
48
|
} else {
|
|
49
|
-
const legacyAuth = event?.context?.
|
|
49
|
+
const legacyAuth = event?.context?.__thunderidAuth;
|
|
50
50
|
authState.value = legacyAuth ?? { isLoading: false, isSignedIn: false, user: null };
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -60,7 +60,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
60
60
|
const user = computed(() => authState.value.user ?? null);
|
|
61
61
|
const organizationRef = computed(() => currentOrgState.value);
|
|
62
62
|
const signIn = async (options) => {
|
|
63
|
-
const returnTo = typeof options?.
|
|
63
|
+
const returnTo = typeof options?.returnTo === "string" ? options.returnTo : void 0;
|
|
64
64
|
const url = returnTo ? `/api/auth/signin?returnTo=${encodeURIComponent(returnTo)}` : "/api/auth/signin";
|
|
65
65
|
await navigateTo(url, { external: true });
|
|
66
66
|
};
|
|
@@ -112,6 +112,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
112
112
|
organizationHandle: publicConfig.organizationHandle,
|
|
113
113
|
platform: void 0,
|
|
114
114
|
reInitialize: async () => false,
|
|
115
|
+
scopes: publicConfig.scopes,
|
|
115
116
|
signIn,
|
|
116
117
|
signInOptions: void 0,
|
|
117
118
|
signInSilently: noop,
|
|
@@ -69,16 +69,15 @@ class ThunderIDNuxtClient extends ThunderIDNodeClient {
|
|
|
69
69
|
if (typeof arg0 === "object" && arg0 !== null && "flowId" in arg0) {
|
|
70
70
|
const sessionId = args[2];
|
|
71
71
|
if (arg0.flowId === "") {
|
|
72
|
-
return this.getSignInUrl(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
72
|
+
return this.getSignInUrl({ client_secret: "{{clientSecret}}", response_mode: "direct" }, sessionId).then(
|
|
73
|
+
(authorizeUrl) => {
|
|
74
|
+
const url = new URL(authorizeUrl);
|
|
75
|
+
return initializeEmbeddedSignInFlow({
|
|
76
|
+
payload: Object.fromEntries(url.searchParams.entries()),
|
|
77
|
+
url: `${url.origin}${url.pathname}`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
);
|
|
82
81
|
}
|
|
83
82
|
const request = args[1] ?? {};
|
|
84
83
|
return executeEmbeddedSignInFlow({
|
|
@@ -161,7 +160,6 @@ class ThunderIDNuxtClient extends ThunderIDNodeClient {
|
|
|
161
160
|
headers: { Authorization: `Bearer ${accessToken}` }
|
|
162
161
|
});
|
|
163
162
|
}
|
|
164
|
-
// eslint-disable-next-line class-methods-use-this
|
|
165
163
|
async getBrandingPreference(config) {
|
|
166
164
|
return getBrandingPreference(config);
|
|
167
165
|
}
|
|
@@ -23,9 +23,9 @@ export default defineNitroPlugin((nitro) => {
|
|
|
23
23
|
);
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
-
const sessionSecret2 = process.env
|
|
26
|
+
const sessionSecret2 = process.env.THUNDERID_SESSION_SECRET || privateConfig?.sessionSecret;
|
|
27
27
|
if (!sessionSecret2) {
|
|
28
|
-
if (process.env
|
|
28
|
+
if (process.env.NODE_ENV === "production") {
|
|
29
29
|
log.error(
|
|
30
30
|
"THUNDERID_SESSION_SECRET is required in production. Set it to a secure random string of at least 32 characters. Refusing to initialize ThunderID client."
|
|
31
31
|
);
|
|
@@ -58,7 +58,7 @@ export default defineNitroPlugin((nitro) => {
|
|
|
58
58
|
const config = useRuntimeConfig(event);
|
|
59
59
|
const publicConfig = config.public.thunderid;
|
|
60
60
|
const prefs = publicConfig?.preferences;
|
|
61
|
-
const sessionSecret = process.env
|
|
61
|
+
const sessionSecret = process.env.THUNDERID_SESSION_SECRET || config.thunderid?.sessionSecret;
|
|
62
62
|
const session = await verifyAndRehydrateSession(
|
|
63
63
|
event,
|
|
64
64
|
sessionSecret
|
|
@@ -77,7 +77,7 @@ export default defineNitroPlugin((nitro) => {
|
|
|
77
77
|
const idToken = await client.getDecodedIdToken(
|
|
78
78
|
session.sessionId
|
|
79
79
|
);
|
|
80
|
-
if (idToken?.
|
|
80
|
+
if (idToken?.user_org) {
|
|
81
81
|
resolvedBaseUrl = `${baseUrl}/o`;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -130,6 +130,6 @@ export default defineNitroPlugin((nitro) => {
|
|
|
130
130
|
isSignedIn: true,
|
|
131
131
|
user: ssrData.user
|
|
132
132
|
};
|
|
133
|
-
eventContext
|
|
133
|
+
eventContext.__thunderidAuth = authState;
|
|
134
134
|
});
|
|
135
135
|
});
|
|
@@ -12,11 +12,11 @@ export default defineEventHandler(async (event) => {
|
|
|
12
12
|
const sessionSecret = config.thunderid?.sessionSecret;
|
|
13
13
|
const publicConfig = config.public.thunderid;
|
|
14
14
|
const query = getQuery(event);
|
|
15
|
-
const code = query
|
|
16
|
-
const state = query
|
|
17
|
-
const sessionState = query
|
|
18
|
-
const error = query
|
|
19
|
-
const errorDescription = query
|
|
15
|
+
const code = query.code;
|
|
16
|
+
const state = query.state;
|
|
17
|
+
const sessionState = query.session_state;
|
|
18
|
+
const error = query.error;
|
|
19
|
+
const errorDescription = query.error_description;
|
|
20
20
|
if (error) {
|
|
21
21
|
throw createError({
|
|
22
22
|
statusCode: 400,
|
|
@@ -8,7 +8,7 @@ export default defineEventHandler(async (event) => {
|
|
|
8
8
|
const config = useRuntimeConfig();
|
|
9
9
|
const sessionSecret = config.thunderid?.sessionSecret;
|
|
10
10
|
const query = getQuery(event);
|
|
11
|
-
const returnTo = query
|
|
11
|
+
const returnTo = query.returnTo;
|
|
12
12
|
const safeReturnTo = returnTo && returnTo.startsWith("/") && !returnTo.startsWith("//") ? returnTo : void 0;
|
|
13
13
|
const sessionId = generateSessionId();
|
|
14
14
|
const tempToken = await createTempSessionToken(sessionId, sessionSecret, safeReturnTo);
|
|
@@ -61,13 +61,13 @@ export declare function getTempSessionCookieName(): string;
|
|
|
61
61
|
/**
|
|
62
62
|
* Session cookie options.
|
|
63
63
|
*/
|
|
64
|
-
|
|
64
|
+
interface SessionCookieOptions {
|
|
65
65
|
httpOnly: boolean;
|
|
66
66
|
maxAge: number;
|
|
67
67
|
path: string;
|
|
68
68
|
sameSite: 'lax';
|
|
69
69
|
secure: boolean;
|
|
70
|
-
}
|
|
70
|
+
}
|
|
71
71
|
export declare function getSessionCookieOptions(): SessionCookieOptions;
|
|
72
72
|
/**
|
|
73
73
|
* Temp session cookie options (15 min TTL).
|
|
@@ -3,9 +3,9 @@ import { setCookie } from "h3";
|
|
|
3
3
|
import { SignJWT, jwtVerify } from "jose";
|
|
4
4
|
const DEFAULT_EXPIRY_SECONDS = 3600;
|
|
5
5
|
function getSecret(sessionSecret) {
|
|
6
|
-
const secret = sessionSecret || process.env
|
|
6
|
+
const secret = sessionSecret || process.env.THUNDERID_SESSION_SECRET;
|
|
7
7
|
if (!secret) {
|
|
8
|
-
if (process.env
|
|
8
|
+
if (process.env.NODE_ENV === "production") {
|
|
9
9
|
throw new Error(
|
|
10
10
|
"[thunderid] THUNDERID_SESSION_SECRET environment variable is required in production. Set it to a secure random string of at least 32 characters."
|
|
11
11
|
);
|
|
@@ -37,7 +37,7 @@ export async function createTempSessionToken(sessionId, sessionSecret, returnTo)
|
|
|
37
37
|
type: "temp"
|
|
38
38
|
};
|
|
39
39
|
if (returnTo) {
|
|
40
|
-
payload
|
|
40
|
+
payload.returnTo = returnTo;
|
|
41
41
|
}
|
|
42
42
|
return new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("15m").sign(secret);
|
|
43
43
|
}
|
|
@@ -49,12 +49,12 @@ export async function verifySessionToken(token, sessionSecret) {
|
|
|
49
49
|
export async function verifyTempSessionToken(token, sessionSecret) {
|
|
50
50
|
const secret = getSecret(sessionSecret);
|
|
51
51
|
const { payload } = await jwtVerify(token, secret);
|
|
52
|
-
if (payload
|
|
52
|
+
if (payload.type !== "temp") {
|
|
53
53
|
throw new Error("Invalid token type: expected temp session");
|
|
54
54
|
}
|
|
55
55
|
return {
|
|
56
|
-
returnTo: payload
|
|
57
|
-
sessionId: payload
|
|
56
|
+
returnTo: payload.returnTo,
|
|
57
|
+
sessionId: payload.sessionId
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
export function getSessionCookieName() {
|
|
@@ -69,7 +69,7 @@ export function getSessionCookieOptions() {
|
|
|
69
69
|
maxAge: DEFAULT_EXPIRY_SECONDS,
|
|
70
70
|
path: "/",
|
|
71
71
|
sameSite: "lax",
|
|
72
|
-
secure: process.env
|
|
72
|
+
secure: process.env.NODE_ENV === "production"
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
export function getTempSessionCookieOptions() {
|
|
@@ -78,7 +78,7 @@ export function getTempSessionCookieOptions() {
|
|
|
78
78
|
maxAge: 15 * 60,
|
|
79
79
|
path: "/",
|
|
80
80
|
sameSite: "lax",
|
|
81
|
-
secure: process.env
|
|
81
|
+
secure: process.env.NODE_ENV === "production"
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
export async function issueSessionCookie(event, sessionId, tokenResponse, sessionSecret) {
|
|
@@ -86,7 +86,7 @@ export async function issueSessionCookie(event, sessionId, tokenResponse, sessio
|
|
|
86
86
|
const client = ThunderIDNuxtClient.getInstance();
|
|
87
87
|
const idToken = await client.getDecodedIdToken(sessionId, tokenResponse.idToken);
|
|
88
88
|
const userId = idToken.sub || sessionId;
|
|
89
|
-
const organizationId = idToken
|
|
89
|
+
const organizationId = idToken.user_org || idToken.organization_id;
|
|
90
90
|
const expiresInSeconds = parseInt(tokenResponse.expiresIn ?? "3600", 10);
|
|
91
91
|
const accessTokenExpiresAt = Math.floor(Date.now() / 1e3) + (Number.isFinite(expiresInSeconds) ? expiresInSeconds : 3600);
|
|
92
92
|
const sessionToken = await createSessionToken(
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface ThunderIDNuxtConfig {
|
|
|
30
30
|
* URL when present. Mirrors `applicationId` in the React/Next.js SDKs.
|
|
31
31
|
*/
|
|
32
32
|
applicationId?: string;
|
|
33
|
-
/** Base URL of the ThunderID org tenant (e.g. https://
|
|
33
|
+
/** Base URL of the ThunderID org tenant (e.g. https://localhost:8090) */
|
|
34
34
|
baseUrl?: string;
|
|
35
35
|
/** OAuth2 Client ID */
|
|
36
36
|
clientId?: string;
|
|
@@ -72,7 +72,7 @@ export interface ThunderIDNuxtConfig {
|
|
|
72
72
|
};
|
|
73
73
|
};
|
|
74
74
|
/** OAuth2 scopes to request */
|
|
75
|
-
scopes?: string[];
|
|
75
|
+
scopes?: string | string[];
|
|
76
76
|
/** Secret for signing session JWTs (use THUNDERID_SESSION_SECRET env var) */
|
|
77
77
|
sessionSecret?: string;
|
|
78
78
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ThunderIDError } from "../errors/thunderid-error.js";
|
|
2
1
|
import { ErrorCode } from "../errors/error-codes.js";
|
|
2
|
+
import { ThunderIDError } from "../errors/thunderid-error.js";
|
|
3
3
|
export function validateReturnUrl(url) {
|
|
4
4
|
if (typeof url !== "string" || url.trim() === "") {
|
|
5
5
|
throw new ThunderIDError("returnTo must be a non-empty string.", ErrorCode.OpenRedirectBlocked, { statusCode: 400 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thunderid/nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Nuxt SDK for ThunderID",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"thunderid",
|
|
@@ -61,24 +61,24 @@
|
|
|
61
61
|
"directory": "packages/nuxt"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@nuxt/kit": "3.21.
|
|
64
|
+
"@nuxt/kit": "3.21.7",
|
|
65
65
|
"defu": "6.1.5",
|
|
66
66
|
"jose": "5.2.0",
|
|
67
|
-
"@thunderid/node": "^0.
|
|
68
|
-
"@thunderid/
|
|
69
|
-
"@thunderid/
|
|
67
|
+
"@thunderid/node": "^0.2.0",
|
|
68
|
+
"@thunderid/vue": "^0.3.0",
|
|
69
|
+
"@thunderid/browser": "^0.3.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@nuxt/devtools": "2.6.4",
|
|
73
73
|
"@nuxt/module-builder": "1.0.1",
|
|
74
|
-
"@nuxt/schema": "3.21.
|
|
74
|
+
"@nuxt/schema": "3.21.7",
|
|
75
75
|
"@nuxt/test-utils": "3.17.2",
|
|
76
76
|
"@types/node": "24.7.2",
|
|
77
77
|
"eslint": "9.39.4",
|
|
78
78
|
"h3": "1.15.11",
|
|
79
|
-
"nuxt": "3.21.
|
|
79
|
+
"nuxt": "3.21.7",
|
|
80
80
|
"typescript": "5.9.3",
|
|
81
|
-
"vitest": "4.1.
|
|
81
|
+
"vitest": "4.1.8",
|
|
82
82
|
"@thunderid/eslint-plugin": "^0.0.0",
|
|
83
83
|
"@thunderid/prettier-config": "^0.0.0"
|
|
84
84
|
},
|
|
@@ -91,6 +91,9 @@
|
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "nuxt module-build prepare && nuxt module-build build",
|
|
94
|
+
"clean": "pnpm clean:dist && pnpm clean:node_modules",
|
|
95
|
+
"clean:dist": "rimraf dist .nuxt",
|
|
96
|
+
"clean:node_modules": "rimraf node_modules",
|
|
94
97
|
"format:check": "prettier --check --cache .",
|
|
95
98
|
"format:fix": "prettier --write --cache .",
|
|
96
99
|
"lint": "nuxt prepare && eslint .",
|