@devcoffee/nuxt-core 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -0
- package/GUIDELINE.md +351 -0
- package/README.md +405 -84
- package/dist/module.d.mts +295 -156
- package/dist/module.d.ts +300 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +191 -36
- package/dist/runtime/app/composables/useAuthContext.d.ts +26 -0
- package/dist/runtime/app/composables/useAuthContext.js +111 -0
- package/dist/runtime/app/composables/useLogger.d.ts +3 -3
- package/dist/runtime/app/composables/useSessionContext.d.ts +22 -0
- package/dist/runtime/app/composables/useSessionContext.js +5 -0
- package/dist/runtime/app/middleware/authts.d.ts +12 -0
- package/dist/runtime/app/middleware/authts.js +101 -0
- package/dist/runtime/app/pages/authorize.d.vue.ts +3 -0
- package/dist/runtime/app/pages/authorize.vue +32 -0
- package/dist/runtime/app/pages/authorize.vue.d.ts +3 -0
- package/dist/runtime/app/plugins/authts.d.ts +82 -0
- package/dist/runtime/app/plugins/authts.js +91 -0
- package/dist/runtime/app/plugins/formatters.d.ts +29 -0
- package/dist/runtime/app/plugins/formatters.js +101 -0
- package/dist/runtime/app/plugins/locale.d.ts +37 -0
- package/dist/runtime/app/plugins/locale.js +39 -0
- package/dist/runtime/app/plugins/logging.d.ts +24 -16
- package/dist/runtime/app/plugins/logging.js +0 -1
- package/dist/runtime/app/utils/hashing.d.ts +1 -0
- package/dist/runtime/app/utils/hashing.js +3 -0
- package/dist/runtime/server/adapters/http.d.ts +5 -0
- package/dist/runtime/server/adapters/http.js +15 -0
- package/dist/runtime/server/adapters/oidc.d.ts +58 -0
- package/dist/runtime/server/adapters/oidc.js +21 -0
- package/dist/runtime/server/adapters/storage.d.ts +39 -0
- package/dist/runtime/server/adapters/storage.js +14 -0
- package/dist/runtime/server/adapters/utils.d.ts +31 -0
- package/dist/runtime/server/adapters/utils.js +28 -0
- package/dist/runtime/server/composables/useServerLogger.d.ts +3 -2
- package/dist/runtime/server/composables/useServerLogger.js +4 -4
- package/dist/runtime/server/core/crypto.d.ts +70 -0
- package/dist/runtime/server/core/crypto.js +55 -0
- package/dist/runtime/server/core/helpers.d.ts +194 -0
- package/dist/runtime/server/core/helpers.js +350 -0
- package/dist/runtime/server/core/index.d.ts +1 -0
- package/dist/runtime/server/core/index.js +1 -0
- package/dist/runtime/server/core/mutex.d.ts +19 -0
- package/dist/runtime/server/core/mutex.js +39 -0
- package/dist/runtime/server/core/nuxtAuthtsHandler.d.ts +26 -0
- package/dist/runtime/server/core/nuxtAuthtsHandler.js +238 -0
- package/dist/runtime/server/core/nuxtForwardHandler.d.ts +18 -0
- package/dist/runtime/server/core/nuxtForwardHandler.js +60 -0
- package/dist/runtime/server/dev/route/session.d.ts +2 -0
- package/dist/runtime/server/dev/route/session.js +8 -0
- package/dist/runtime/server/plugins/authts.d.ts +11 -0
- package/dist/runtime/server/plugins/authts.js +55 -0
- package/dist/runtime/server/plugins/logging.js +7 -2
- package/dist/runtime/server/tsconfig.json +3 -3
- package/dist/runtime/types/global.env.d.ts +21 -7
- package/dist/runtime/types/nitro.d.ts +7 -2
- package/dist/runtime/types/nuxt.d.ts +28 -8
- package/dist/runtime/utils.d.ts +31 -0
- package/dist/runtime/utils.js +28 -0
- package/dist/types.d.mts +6 -4
- package/package.json +45 -17
- package/dist/runtime/plugin.d.ts +0 -2
- package/dist/runtime/plugin.js +0 -4
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useRuntimeConfig, useState } from "#app";
|
|
2
|
+
import { useAuthContext, useLogger, watch } from "#imports";
|
|
3
|
+
export default defineNuxtPlugin(async (_nuxtApp) => {
|
|
4
|
+
const logger = useLogger({ tag: "plugin.locale", level: 3 });
|
|
5
|
+
const { defaultLocale, defaultLanguage, defaultTimeZone } = useRuntimeConfig().public.nuxtCore;
|
|
6
|
+
const localeState = useState("devecoffee.locale", () => ({
|
|
7
|
+
locale: defaultLocale,
|
|
8
|
+
language: defaultLanguage,
|
|
9
|
+
timeZone: defaultTimeZone
|
|
10
|
+
}));
|
|
11
|
+
watch(
|
|
12
|
+
localeState,
|
|
13
|
+
async (value) => {
|
|
14
|
+
await _nuxtApp.callHook("dfLocale:changed", value);
|
|
15
|
+
},
|
|
16
|
+
{ deep: true }
|
|
17
|
+
);
|
|
18
|
+
_nuxtApp.hooks.addHooks({
|
|
19
|
+
"app:created": () => {
|
|
20
|
+
const { user } = useAuthContext();
|
|
21
|
+
[localeState.value.locale, localeState.value.language, localeState.value.timeZone] = [
|
|
22
|
+
user.value.locale,
|
|
23
|
+
user.value.language,
|
|
24
|
+
user.value.timezone
|
|
25
|
+
];
|
|
26
|
+
logger.debug(`App created, updating locale = '${JSON.stringify(localeState.value)}'.`);
|
|
27
|
+
},
|
|
28
|
+
"session:changed": (session) => {
|
|
29
|
+
const { locale, language, timezone } = session.user;
|
|
30
|
+
[localeState.value.locale, localeState.value.language, localeState.value.timeZone] = [locale, language, timezone];
|
|
31
|
+
logger.debug(`Session changed, updating locale = '${JSON.stringify(localeState.value)}'.`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
_nuxtApp.provide("dfLocale", {
|
|
35
|
+
locale: localeState.value.locale,
|
|
36
|
+
language: localeState.value.language,
|
|
37
|
+
timeZone: localeState.value.timeZone
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
declare
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import type { NuxtCoreLogging } from '@devcoffee/nuxt-core';
|
|
2
|
+
declare module 'vue' {
|
|
3
|
+
/**
|
|
4
|
+
* Provides access to logging utilities.
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
interface ComponentCustomProperties {
|
|
8
|
+
$logging: NuxtCoreLogging;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
declare module '#app' {
|
|
12
|
+
interface NuxtApp {
|
|
13
|
+
/**
|
|
14
|
+
* Provides access to logging utilities.
|
|
15
|
+
* @since 1.0.0
|
|
16
|
+
*/
|
|
17
|
+
$logging: NuxtCoreLogging;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 🧩 Nuxt plugin for logging utilities.
|
|
22
|
+
* @since 1.0.0
|
|
23
|
+
*/
|
|
24
|
+
declare const _default: any;
|
|
17
25
|
export default _default;
|
|
@@ -7,7 +7,6 @@ function initLogger() {
|
|
|
7
7
|
config = useRuntimeConfig(useRequestEvent()).nuxtCore.logging.ssr;
|
|
8
8
|
} else {
|
|
9
9
|
config = useRuntimeConfig().public.nuxtCore.logging;
|
|
10
|
-
console.log("Initializing logger with tag:", config);
|
|
11
10
|
}
|
|
12
11
|
const { tag = "app", ...opts } = config;
|
|
13
12
|
return consola.create(opts).withTag(tag);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function randomShortHash(length?: number): string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createError, deleteCookie, eventHandler, getCookie, getQuery, getRequestHeaders, getRequestURL, proxyRequest, readBody, readFormData, sendNoContent, setCookie, } from 'h3';
|
|
2
|
+
export type { EventHandlerRequest, H3Error, H3Event, ProxyOptions, RequestHeaders } from 'h3';
|
|
3
|
+
export { defineNitroPlugin, useRuntimeConfig } from 'nitropack/runtime';
|
|
4
|
+
export type { CoreLogLevel, NuxtAuthOptions } from '@devcoffee/nuxt-core';
|
|
5
|
+
export type { CookieSerializeOptions } from 'cookie-es';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createError,
|
|
3
|
+
deleteCookie,
|
|
4
|
+
eventHandler,
|
|
5
|
+
getCookie,
|
|
6
|
+
getQuery,
|
|
7
|
+
getRequestHeaders,
|
|
8
|
+
getRequestURL,
|
|
9
|
+
proxyRequest,
|
|
10
|
+
readBody,
|
|
11
|
+
readFormData,
|
|
12
|
+
sendNoContent,
|
|
13
|
+
setCookie
|
|
14
|
+
} from "h3";
|
|
15
|
+
export { defineNitroPlugin, useRuntimeConfig } from "nitropack/runtime";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as client from 'openid-client';
|
|
2
|
+
export type { TokenEndpointResponse, AuthorizationCodeGrantChecks } from 'openid-client';
|
|
3
|
+
export type { ServerMetadata, ClientMetadata } from 'openid-client';
|
|
4
|
+
export { Configuration } from 'openid-client';
|
|
5
|
+
/**
|
|
6
|
+
* Module-owned type for OIDC userinfo response fields used by this module.
|
|
7
|
+
* Fields match exactly what helpers.fetchUserInfo() returns after mapping.
|
|
8
|
+
* No index signature — named fields only (avoids TypeScript index signature conflicts).
|
|
9
|
+
*
|
|
10
|
+
* @since 1.0.0
|
|
11
|
+
*/
|
|
12
|
+
export interface OidcUserInfo {
|
|
13
|
+
sub: string;
|
|
14
|
+
email: string;
|
|
15
|
+
id: string;
|
|
16
|
+
firstName: string;
|
|
17
|
+
lastName: string;
|
|
18
|
+
}
|
|
19
|
+
/** @since 1.0.0 */
|
|
20
|
+
export declare const discovery: typeof client.discovery;
|
|
21
|
+
/**
|
|
22
|
+
* Allows `openid-client` to use HTTP (non-TLS) discovery and token endpoints.
|
|
23
|
+
* Pass as `execute: [allowInsecureRequests]` in discovery options.
|
|
24
|
+
* Required for test fixtures that run a mock OIDC server on http://.
|
|
25
|
+
* Never pass this in production — production always uses HTTPS endpoints.
|
|
26
|
+
* @since 1.0.0
|
|
27
|
+
*/
|
|
28
|
+
export declare const allowInsecureRequests: typeof client.allowInsecureRequests;
|
|
29
|
+
/** @since 1.0.0 */
|
|
30
|
+
export declare const randomState: typeof client.randomState;
|
|
31
|
+
/** @since 1.0.0 */
|
|
32
|
+
export declare const randomPKCECodeVerifier: typeof client.randomPKCECodeVerifier;
|
|
33
|
+
/** @since 1.0.0 */
|
|
34
|
+
export declare const calculatePKCECodeChallenge: typeof client.calculatePKCECodeChallenge;
|
|
35
|
+
/** @since 1.0.0 */
|
|
36
|
+
export declare const buildAuthorizationUrl: typeof client.buildAuthorizationUrl;
|
|
37
|
+
/** @since 1.0.0 */
|
|
38
|
+
export declare const authorizationCodeGrant: typeof client.authorizationCodeGrant;
|
|
39
|
+
/** @since 1.0.0 */
|
|
40
|
+
export declare const refreshTokenGrant: typeof client.refreshTokenGrant;
|
|
41
|
+
/**
|
|
42
|
+
* Revokes an OAuth token via the token revocation endpoint.
|
|
43
|
+
* Wraps client.tokenRevocation with the module-owned function name.
|
|
44
|
+
*
|
|
45
|
+
* @since 1.0.0
|
|
46
|
+
*/
|
|
47
|
+
export declare const revokeToken: typeof client.tokenRevocation;
|
|
48
|
+
/**
|
|
49
|
+
* Fetches user info from the OIDC provider and maps to module-owned OidcUserInfo type.
|
|
50
|
+
* Internalizes all casts from UserInfoResponse non-standard fields (id, firstName, lastName).
|
|
51
|
+
*
|
|
52
|
+
* @param config - The openid-client Configuration instance.
|
|
53
|
+
* @param accessToken - The access token to use for the userinfo request.
|
|
54
|
+
* @param sub - The subject identifier of the authenticated user.
|
|
55
|
+
* @returns Mapped OidcUserInfo with empty strings for any missing optional fields.
|
|
56
|
+
* @since 1.0.0
|
|
57
|
+
*/
|
|
58
|
+
export declare function fetchUserInfo(config: client.Configuration, accessToken: string, sub: string): Promise<OidcUserInfo>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as client from "openid-client";
|
|
2
|
+
export { Configuration } from "openid-client";
|
|
3
|
+
export const discovery = client.discovery;
|
|
4
|
+
export const allowInsecureRequests = client.allowInsecureRequests;
|
|
5
|
+
export const randomState = client.randomState;
|
|
6
|
+
export const randomPKCECodeVerifier = client.randomPKCECodeVerifier;
|
|
7
|
+
export const calculatePKCECodeChallenge = client.calculatePKCECodeChallenge;
|
|
8
|
+
export const buildAuthorizationUrl = client.buildAuthorizationUrl;
|
|
9
|
+
export const authorizationCodeGrant = client.authorizationCodeGrant;
|
|
10
|
+
export const refreshTokenGrant = client.refreshTokenGrant;
|
|
11
|
+
export const revokeToken = client.tokenRevocation;
|
|
12
|
+
export async function fetchUserInfo(config, accessToken, sub) {
|
|
13
|
+
const oidUser = await client.fetchUserInfo(config, accessToken, sub);
|
|
14
|
+
return {
|
|
15
|
+
id: oidUser["id"] || "",
|
|
16
|
+
sub: oidUser.sub,
|
|
17
|
+
email: oidUser.email || "",
|
|
18
|
+
firstName: oidUser["firstName"] || "",
|
|
19
|
+
lastName: oidUser["lastName"] || ""
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SessionContext } from '@devcoffee/nuxt-core';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves session data from Nitro storage.
|
|
4
|
+
*
|
|
5
|
+
* @param storageName - The Nitro storage name (e.g. 'session').
|
|
6
|
+
* @param key - The storage key including prefix (e.g. 'prefix:sessionId').
|
|
7
|
+
* @returns The stored {@link SessionContext} if found, otherwise `null`.
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
export declare function getSessionData(storageName: string, key: string): Promise<SessionContext | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Writes session data to Nitro storage with optional TTL.
|
|
13
|
+
*
|
|
14
|
+
* @param storageName - The Nitro storage name.
|
|
15
|
+
* @param key - The storage key including prefix.
|
|
16
|
+
* @param data - The {@link SessionContext} to store.
|
|
17
|
+
* @param ttl - Optional TTL in seconds. When omitted, storage default applies.
|
|
18
|
+
* @returns A promise that resolves when the write completes.
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
export declare function setSessionData(storageName: string, key: string, data: SessionContext, ttl?: number): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Removes session data from Nitro storage.
|
|
24
|
+
*
|
|
25
|
+
* @param storageName - The Nitro storage name.
|
|
26
|
+
* @param key - The storage key including prefix.
|
|
27
|
+
* @returns A promise that resolves when removal completes.
|
|
28
|
+
* @since 1.0.0
|
|
29
|
+
*/
|
|
30
|
+
export declare function removeSessionData(storageName: string, key: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Checks whether session data exists in Nitro storage.
|
|
33
|
+
*
|
|
34
|
+
* @param storageName - The Nitro storage name.
|
|
35
|
+
* @param key - The storage key including prefix.
|
|
36
|
+
* @returns `true` if the key exists, `false` otherwise.
|
|
37
|
+
* @since 1.0.0
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasSessionData(storageName: string, key: string): Promise<boolean>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useStorage } from "nitropack/runtime";
|
|
2
|
+
export async function getSessionData(storageName, key) {
|
|
3
|
+
return useStorage(storageName).getItem(key);
|
|
4
|
+
}
|
|
5
|
+
export async function setSessionData(storageName, key, data, ttl) {
|
|
6
|
+
const opts = ttl !== void 0 ? { ttl } : void 0;
|
|
7
|
+
return useStorage(storageName).setItem(key, data, opts);
|
|
8
|
+
}
|
|
9
|
+
export async function removeSessionData(storageName, key) {
|
|
10
|
+
return useStorage(storageName).removeItem(key);
|
|
11
|
+
}
|
|
12
|
+
export async function hasSessionData(storageName, key) {
|
|
13
|
+
return useStorage(storageName).hasItem(key);
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively merges source objects into a new object based on target shape.
|
|
3
|
+
* Matches lodash merge semantics: arrays overwrite (no concat), undefined values skipped.
|
|
4
|
+
* Does NOT mutate the target argument.
|
|
5
|
+
*
|
|
6
|
+
* @param target - The base object (not mutated).
|
|
7
|
+
* @param sources - One or more partial source objects to merge in order.
|
|
8
|
+
* @returns A new merged object.
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
export declare function deepMerge<T extends object>(target: T, ...sources: object[]): T;
|
|
12
|
+
/**
|
|
13
|
+
* Returns a new object excluding the specified keys.
|
|
14
|
+
* Does NOT mutate the source object.
|
|
15
|
+
*
|
|
16
|
+
* @param obj - The source object.
|
|
17
|
+
* @param keys - Array of keys to exclude.
|
|
18
|
+
* @returns A new object without the excluded keys.
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
export declare function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K>;
|
|
22
|
+
/**
|
|
23
|
+
* Returns a new object containing only the specified keys.
|
|
24
|
+
* Does NOT mutate the source object.
|
|
25
|
+
*
|
|
26
|
+
* @param obj - The source object.
|
|
27
|
+
* @param keys - Array of keys to include.
|
|
28
|
+
* @returns A new object with only the picked keys.
|
|
29
|
+
* @since 1.0.0
|
|
30
|
+
*/
|
|
31
|
+
export declare function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function deepMerge(target, ...sources) {
|
|
2
|
+
const result = { ...target };
|
|
3
|
+
for (const source of sources) {
|
|
4
|
+
if (!source || typeof source !== "object") continue;
|
|
5
|
+
for (const key of Object.keys(source)) {
|
|
6
|
+
const srcVal = source[key];
|
|
7
|
+
const tgtVal = result[key];
|
|
8
|
+
if (srcVal !== null && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal !== null && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
|
|
9
|
+
result[key] = deepMerge(tgtVal, srcVal);
|
|
10
|
+
} else if (srcVal !== void 0) {
|
|
11
|
+
result[key] = srcVal;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
export function omit(obj, keys) {
|
|
18
|
+
return Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k)));
|
|
19
|
+
}
|
|
20
|
+
export function pick(obj, keys) {
|
|
21
|
+
const result = {};
|
|
22
|
+
for (const key of keys) {
|
|
23
|
+
if (key in obj) {
|
|
24
|
+
result[key] = obj[key];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ConsolaInstance, type LogLevel } from 'consola';
|
|
2
2
|
import type { H3Event } from 'h3';
|
|
3
|
-
export default function useServerLogger(
|
|
3
|
+
export default function useServerLogger(options?: Partial<{
|
|
4
|
+
event?: H3Event;
|
|
4
5
|
tag?: string;
|
|
5
6
|
level: LogLevel;
|
|
6
|
-
}): ConsolaInstance;
|
|
7
|
+
}>): ConsolaInstance;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { consola } from "consola";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
3
|
let globalServerLogger;
|
|
4
|
-
export default function useServerLogger(
|
|
4
|
+
export default function useServerLogger(options) {
|
|
5
|
+
const { tag, ...opts } = useRuntimeConfig(options?.event).nuxtCore.logging.server;
|
|
5
6
|
if (!globalServerLogger) {
|
|
6
|
-
|
|
7
|
-
globalServerLogger = consola.create(opts2).withTag(tag2);
|
|
7
|
+
globalServerLogger = consola.create(opts).withTag(tag);
|
|
8
8
|
}
|
|
9
|
-
const { tag, level } = opts || {};
|
|
10
9
|
let _logger = globalServerLogger;
|
|
10
|
+
const { level } = opts || {};
|
|
11
11
|
if (level != void 0) {
|
|
12
12
|
_logger = _logger.create({ level });
|
|
13
13
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Shape of an AES-256-GCM encrypted tokenSet stored in Nitro storage. */
|
|
2
|
+
export type EncryptedTokenSet = {
|
|
3
|
+
encrypted: true;
|
|
4
|
+
iv: string;
|
|
5
|
+
authTag: string;
|
|
6
|
+
ciphertext: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Generates a cryptographically secure 256-bit session ID.
|
|
10
|
+
* Returns a 64-character lowercase hex string.
|
|
11
|
+
* Replaces uuid v4 generation (SEC-04, D-04).
|
|
12
|
+
*
|
|
13
|
+
* @returns {string} A 64-character lowercase hex string.
|
|
14
|
+
* @since 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateSessionId(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Validates that a session ID matches the expected hex-64 format.
|
|
19
|
+
* Replaces uuid.validate() (SEC-04, D-05).
|
|
20
|
+
* UUID v4 format (36 chars with hyphens) is intentionally rejected.
|
|
21
|
+
*
|
|
22
|
+
* @param id - The session ID string to validate.
|
|
23
|
+
* @returns {boolean} True if the ID is a valid 64-character lowercase hex string.
|
|
24
|
+
* @since 1.0.0
|
|
25
|
+
*/
|
|
26
|
+
export declare function isValidSessionId(id: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* HMAC-SHA256 signs a session ID for use as a cookie value.
|
|
29
|
+
* Returns '{sessionId}.{hmacHex}' — exactly 129 characters (SEC-03, D-07, D-08).
|
|
30
|
+
*
|
|
31
|
+
* @param sessionId - The 64-character hex session ID to sign.
|
|
32
|
+
* @param secret - The HMAC signing secret from sessions.secret config.
|
|
33
|
+
* @returns {string} Signed cookie value in '{sessionId}.{hmac}' format.
|
|
34
|
+
* @since 1.0.0
|
|
35
|
+
*/
|
|
36
|
+
export declare function signSessionId(sessionId: string, secret: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Verifies a session cookie value and returns the raw session ID if valid.
|
|
39
|
+
* Returns null if the cookie is undefined, malformed, or the HMAC does not match.
|
|
40
|
+
* When secret is empty, passes through the raw value for backward-compat (D-09).
|
|
41
|
+
* Uses timingSafeEqual to prevent timing attacks.
|
|
42
|
+
*
|
|
43
|
+
* @param cookieValue - The raw session cookie value from the request.
|
|
44
|
+
* @param secret - The HMAC signing secret from sessions.secret config.
|
|
45
|
+
* @returns {string | null} The session ID if valid, or null on failure.
|
|
46
|
+
* @since 1.0.0
|
|
47
|
+
*/
|
|
48
|
+
export declare function verifySessionId(cookieValue: string | undefined, secret: string): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Encrypts a tokenSet object with AES-256-GCM using a key derived from sessions.secret.
|
|
51
|
+
* A random 12-byte IV is generated per encryption operation (SEC-05, D-12, D-13).
|
|
52
|
+
* The returned shape is stored verbatim in the tokenSet field of Nitro storage.
|
|
53
|
+
*
|
|
54
|
+
* @param tokenSet - The token set object to encrypt.
|
|
55
|
+
* @param secret - The encryption secret from sessions.secret config.
|
|
56
|
+
* @returns {EncryptedTokenSet} The encrypted token set with iv, authTag, and ciphertext.
|
|
57
|
+
* @since 1.0.0
|
|
58
|
+
*/
|
|
59
|
+
export declare function encryptTokenSet(tokenSet: object, secret: string): EncryptedTokenSet;
|
|
60
|
+
/**
|
|
61
|
+
* Decrypts an EncryptedTokenSet and returns the parsed object, or null on any failure.
|
|
62
|
+
* Returns null on auth tag mismatch (tamper detection), malformed data, or any crypto error.
|
|
63
|
+
* Callers must treat null as inaccessible tokenSet — do not throw (SEC-05, D-15).
|
|
64
|
+
*
|
|
65
|
+
* @param encrypted - The encrypted token set to decrypt.
|
|
66
|
+
* @param secret - The decryption secret from sessions.secret config.
|
|
67
|
+
* @returns {object | null} The decrypted token set object, or null on failure.
|
|
68
|
+
* @since 1.0.0
|
|
69
|
+
*/
|
|
70
|
+
export declare function decryptTokenSet(encrypted: EncryptedTokenSet, secret: string): object | null;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHmac, hkdfSync, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
function deriveAesKey(secret) {
|
|
3
|
+
return Buffer.from(hkdfSync("sha256", secret, Buffer.alloc(0), "devcoffee-authts-tokenset-v1", 32));
|
|
4
|
+
}
|
|
5
|
+
export function generateSessionId() {
|
|
6
|
+
return randomBytes(32).toString("hex");
|
|
7
|
+
}
|
|
8
|
+
export function isValidSessionId(id) {
|
|
9
|
+
return /^[0-9a-f]{64}$/i.test(id);
|
|
10
|
+
}
|
|
11
|
+
export function signSessionId(sessionId, secret) {
|
|
12
|
+
const sig = createHmac("sha256", secret).update(sessionId).digest("hex");
|
|
13
|
+
return `${sessionId}.${sig}`;
|
|
14
|
+
}
|
|
15
|
+
export function verifySessionId(cookieValue, secret) {
|
|
16
|
+
if (!cookieValue) return null;
|
|
17
|
+
if (!secret) return cookieValue;
|
|
18
|
+
const dotIndex = cookieValue.lastIndexOf(".");
|
|
19
|
+
if (dotIndex !== 64 || cookieValue.length !== 129) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const sessionId = cookieValue.slice(0, 64);
|
|
23
|
+
const providedSig = cookieValue.slice(65);
|
|
24
|
+
const expected = createHmac("sha256", secret).update(sessionId).digest("hex");
|
|
25
|
+
const valid = timingSafeEqual(Buffer.from(providedSig, "hex"), Buffer.from(expected, "hex"));
|
|
26
|
+
return valid ? sessionId : null;
|
|
27
|
+
}
|
|
28
|
+
export function encryptTokenSet(tokenSet, secret) {
|
|
29
|
+
const key = deriveAesKey(secret);
|
|
30
|
+
const iv = randomBytes(12);
|
|
31
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
32
|
+
const ciphertext = Buffer.concat([cipher.update(JSON.stringify(tokenSet), "utf8"), cipher.final()]);
|
|
33
|
+
const authTag = cipher.getAuthTag();
|
|
34
|
+
return {
|
|
35
|
+
encrypted: true,
|
|
36
|
+
iv: iv.toString("hex"),
|
|
37
|
+
authTag: authTag.toString("hex"),
|
|
38
|
+
ciphertext: ciphertext.toString("hex")
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function decryptTokenSet(encrypted, secret) {
|
|
42
|
+
try {
|
|
43
|
+
const key = deriveAesKey(secret);
|
|
44
|
+
const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(encrypted.iv, "hex"));
|
|
45
|
+
decipher.setAuthTag(Buffer.from(encrypted.authTag, "hex"));
|
|
46
|
+
const plain = Buffer.concat([
|
|
47
|
+
decipher.update(Buffer.from(encrypted.ciphertext, "hex")),
|
|
48
|
+
decipher.final()
|
|
49
|
+
// throws synchronously if auth tag invalid
|
|
50
|
+
]).toString("utf8");
|
|
51
|
+
return JSON.parse(plain);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { SessionContext } from '@devcoffee/nuxt-core';
|
|
2
|
+
import type { ClientMetadata, ServerMetadata, TokenEndpointResponse } from '#devcoffee-core/server/adapters/oidc';
|
|
3
|
+
/** Options used for session creation and validation. */
|
|
4
|
+
type SessionCreateOptions = {
|
|
5
|
+
storageName: string;
|
|
6
|
+
storagePrefix: string;
|
|
7
|
+
expiresIn: number;
|
|
8
|
+
secret: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Check whether a candidate redirect URL is same-origin with the request URL.
|
|
12
|
+
*
|
|
13
|
+
* Uses the WHATWG URL constructor — malformed URLs and relative paths return false
|
|
14
|
+
* instead of throwing, guarding against attacker-controlled input.
|
|
15
|
+
*
|
|
16
|
+
* @param redirectUrl - The candidate redirect URL string (from query param or cookie).
|
|
17
|
+
* @param requestUrl - The trusted request URL from the h3 event.
|
|
18
|
+
* @returns `true` if both URLs share the same origin, `false` otherwise.
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
export declare function isSameOrigin(redirectUrl: string, requestUrl: URL): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Retrieve an existing session from storage.
|
|
24
|
+
*
|
|
25
|
+
* @param sessionId - The session ID string.
|
|
26
|
+
* @param opts - Options containing the storage prefix.
|
|
27
|
+
* @returns The stored {@link SessionContext} if found, otherwise `null`.
|
|
28
|
+
* @since 1.0.0
|
|
29
|
+
*/
|
|
30
|
+
export declare function getSession(sessionId: string, opts: Pick<SessionCreateOptions, 'storagePrefix' | 'storageName' | 'secret'>): Promise<any>;
|
|
31
|
+
/**
|
|
32
|
+
* Validate or create a new session.
|
|
33
|
+
*
|
|
34
|
+
* - If the provided cookie value is missing or invalid, a new UUID is generated.
|
|
35
|
+
* - Expired sessions are reinitialized with a default unauthenticated context.
|
|
36
|
+
* - Session expiry is automatically updated and persisted.
|
|
37
|
+
*
|
|
38
|
+
* @param sessionCookieId - The session cookie value (may be undefined).
|
|
39
|
+
* @param opts - Session creation options including prefix and expiration time.
|
|
40
|
+
* @returns Object containing the resolved session ID and expiry date.
|
|
41
|
+
* @since 1.0.0
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateSession(sessionCookieId: string | undefined, opts: SessionCreateOptions): Promise<SessionContext>;
|
|
44
|
+
/**
|
|
45
|
+
* Update an existing session’s properties and refresh its expiry.
|
|
46
|
+
*
|
|
47
|
+
* Throws if the session ID does not exist.
|
|
48
|
+
*
|
|
49
|
+
* @param sessionId - The session ID.
|
|
50
|
+
* @param input - Partial update fields for the session (excluding ID, issuedAt, expiresAt).
|
|
51
|
+
* @param opts - Expiration configuration (in milliseconds).
|
|
52
|
+
* @returns The updated {@link SessionContext}.
|
|
53
|
+
* @throws {Error} If session cannot be found in storage.
|
|
54
|
+
* @since 1.0.0
|
|
55
|
+
*/
|
|
56
|
+
export declare function updateSession(sessionId: string, input: DeepPartial<Omit<SessionContext, 'id' | 'expiresAt' | 'issuedAt'>>, opts: SessionCreateOptions): Promise<SessionContext>;
|
|
57
|
+
export declare function renewSession(sessionId: string, opts: SessionCreateOptions): Promise<SessionContext>;
|
|
58
|
+
/**
|
|
59
|
+
* Delete a session from storage by ID.
|
|
60
|
+
*
|
|
61
|
+
* Removes the session entry from Nitro storage. Does NOT create a replacement.
|
|
62
|
+
* The Nitro request hook (`validateSession`) will create a fresh anonymous session
|
|
63
|
+
* on the next incoming request naturally.
|
|
64
|
+
*
|
|
65
|
+
* Used by the LOGOUT action to ensure the old session ID resolves to nothing
|
|
66
|
+
* in storage after logout — closes the zombie-session gap (SEC-02).
|
|
67
|
+
*
|
|
68
|
+
* @param sessionId - The session ID to delete.
|
|
69
|
+
* @param opts - Options containing the storage name and prefix.
|
|
70
|
+
* @returns A promise that resolves when deletion is complete (void).
|
|
71
|
+
* @since 1.0.0
|
|
72
|
+
*/
|
|
73
|
+
export declare function deleteSession(sessionId: string, opts: Pick<SessionCreateOptions, 'storageName' | 'storagePrefix'>): Promise<void>;
|
|
74
|
+
/** Options for OpenID Connect discovery and caching. */
|
|
75
|
+
type OpenIdDiscoveryOptions = {
|
|
76
|
+
cache: {
|
|
77
|
+
/** Cache key prefix. */
|
|
78
|
+
prefix: string;
|
|
79
|
+
/** Cache expiration time in seconds. */
|
|
80
|
+
expires: number;
|
|
81
|
+
};
|
|
82
|
+
clientId: string;
|
|
83
|
+
clientSecret: string;
|
|
84
|
+
};
|
|
85
|
+
/** Structure of stored OpenID metadata. */
|
|
86
|
+
type OpenIDMetaStorageValue = {
|
|
87
|
+
server: ServerMetadata;
|
|
88
|
+
client: ClientMetadata;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Discover OpenID Connect configuration and cache it.
|
|
92
|
+
*
|
|
93
|
+
* Fetches `.well-known/openid-configuration` from the issuer if not cached.
|
|
94
|
+
*
|
|
95
|
+
* @param wellKnownUrl - The OpenID Connect discovery endpoint.
|
|
96
|
+
* @param opts - Discovery and cache options.
|
|
97
|
+
* @returns The cached or newly fetched server/client metadata.
|
|
98
|
+
* @since 1.0.0
|
|
99
|
+
*/
|
|
100
|
+
export declare function discoveryOpendId(wellKnownUrl: string, opts: OpenIdDiscoveryOptions): Promise<OpenIDMetaStorageValue>;
|
|
101
|
+
/**
|
|
102
|
+
* Retrieve a reusable OpenID configuration object.
|
|
103
|
+
*
|
|
104
|
+
* @param wellKnownUrl - The OpenID Connect discovery endpoint.
|
|
105
|
+
* @param opts - Discovery and client configuration options.
|
|
106
|
+
* @returns An initialized `openid-client` Configuration instance.
|
|
107
|
+
* @since 1.0.0
|
|
108
|
+
*/
|
|
109
|
+
export declare function getOpenIdConfiguration(wellKnownUrl: string, opts: OpenIdDiscoveryOptions): Promise<any>;
|
|
110
|
+
/** Result of a built authorization URL request. */
|
|
111
|
+
type AuthorizationUrlResult = {
|
|
112
|
+
state: string;
|
|
113
|
+
authorizeUrl: string;
|
|
114
|
+
pkceCodeVerifier?: string;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Build an OpenID Connect authorization URL and update session status.
|
|
118
|
+
*
|
|
119
|
+
* Handles optional PKCE challenge and state generation.
|
|
120
|
+
*
|
|
121
|
+
* @param session - The current session context.
|
|
122
|
+
* @param opt - OpenID configuration and authorization parameters.
|
|
123
|
+
* @returns The generated authorization URL, state, and optional PKCE verifier.
|
|
124
|
+
* @since 1.0.0
|
|
125
|
+
*/
|
|
126
|
+
export declare function buildAuthorizationUrl(session: SessionContext, opt: OpenIdDiscoveryOptions & {
|
|
127
|
+
origin: URL;
|
|
128
|
+
wellKnownUrl: string;
|
|
129
|
+
redirectUri: string;
|
|
130
|
+
usePkce: boolean;
|
|
131
|
+
scopes: string[];
|
|
132
|
+
codeChallengeMethod: string;
|
|
133
|
+
sessionStorageName: string;
|
|
134
|
+
sessionStoragePrefix: string;
|
|
135
|
+
sessionExpires: number;
|
|
136
|
+
sessionSecret?: string;
|
|
137
|
+
}): Promise<AuthorizationUrlResult>;
|
|
138
|
+
/**
|
|
139
|
+
* Exchange an authorization code for tokens via the Authorization Code Grant.
|
|
140
|
+
*
|
|
141
|
+
* @param authorizeParams - Authorization code, state, and PKCE verification values.
|
|
142
|
+
* @param opts - OpenID discovery and PKCE configuration.
|
|
143
|
+
* @returns Token response from the OpenID Provider.
|
|
144
|
+
* @since 1.0.0
|
|
145
|
+
*/
|
|
146
|
+
export declare function authorizationCodeGrant(authorizeParams: {
|
|
147
|
+
code?: string;
|
|
148
|
+
state?: string;
|
|
149
|
+
checks: {
|
|
150
|
+
expectedState?: string;
|
|
151
|
+
pkceCodeVerifier?: string;
|
|
152
|
+
};
|
|
153
|
+
}, opts: OpenIdDiscoveryOptions & {
|
|
154
|
+
codeChallengeMethod: string;
|
|
155
|
+
wellKnownUrl: string;
|
|
156
|
+
origin: URL;
|
|
157
|
+
redirectUri: string;
|
|
158
|
+
usePkce: boolean;
|
|
159
|
+
}): Promise<any>;
|
|
160
|
+
export declare function constructTokenSet(input: TokenEndpointResponse): {
|
|
161
|
+
tokenType: string;
|
|
162
|
+
idToken: any;
|
|
163
|
+
accessToken: any;
|
|
164
|
+
refreshToken: any;
|
|
165
|
+
scopes: any;
|
|
166
|
+
expiresAt: number;
|
|
167
|
+
};
|
|
168
|
+
export declare function refreshTokenIfNeeded(session: SessionContext, opts: {
|
|
169
|
+
wellKnownUrl: string;
|
|
170
|
+
cache: {
|
|
171
|
+
prefix: string;
|
|
172
|
+
expires: number;
|
|
173
|
+
};
|
|
174
|
+
clientId: string;
|
|
175
|
+
clientSecret: string;
|
|
176
|
+
tokenRefreshBufferMs: number;
|
|
177
|
+
distributedLock?: boolean;
|
|
178
|
+
}): Promise<any>;
|
|
179
|
+
/**
|
|
180
|
+
* Fetch user profile information from the OpenID Provider using the access token.
|
|
181
|
+
*
|
|
182
|
+
* @param tokenSet - The token response returned from the token endpoint.
|
|
183
|
+
* @param sub - The subject identifier of the authenticated user.
|
|
184
|
+
* @param opts - OpenID discovery and client configuration options.
|
|
185
|
+
* @returns Simplified user information (id, sub, email, first/last name).
|
|
186
|
+
* @since 1.0.0
|
|
187
|
+
*/
|
|
188
|
+
export declare function fetchUserInfo(accessToken: string, sub: string, opts: OpenIdDiscoveryOptions & {
|
|
189
|
+
wellKnownUrl: string;
|
|
190
|
+
}): Promise<any>;
|
|
191
|
+
export declare function revokeTokens(tokens: string[], opts: OpenIdDiscoveryOptions & {
|
|
192
|
+
wellKnownUrl: string;
|
|
193
|
+
}): Promise<PromiseSettledResult<any>[]>;
|
|
194
|
+
export {};
|