@devcoffee/nuxt-core 1.5.1 → 1.6.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 +30 -0
- package/README.md +8 -2
- package/dist/module.d.mts +56 -1
- package/dist/module.d.ts +56 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +63 -4
- package/dist/runtime/app/composables/useLogger.d.ts +12 -0
- package/dist/runtime/app/plugins/formatters.d.ts +30 -0
- package/dist/runtime/app/plugins/formatters.js +22 -8
- package/dist/runtime/app/plugins/locale.d.ts +6 -0
- package/dist/runtime/app/utils/hashing.d.ts +7 -0
- package/dist/runtime/server/composables/useServerLogger.d.ts +12 -0
- package/dist/runtime/server/core/helpers.d.ts +36 -1
- package/dist/runtime/server/dev/route/session.d.ts +1 -1
- package/dist/runtime/server/dev/route/session.js +280 -3
- package/dist/runtime/server/plugins/authts.js +31 -9
- package/dist/runtime/server/plugins/logging.d.ts +9 -0
- package/dist/types.d.mts +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.6.1
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.6.0...v1.6.1)
|
|
6
|
+
|
|
7
|
+
### 🩹 Fixes
|
|
8
|
+
|
|
9
|
+
- Enhance Nuxt app integration by adding $formatters to NuxtApp interface ([e4e3bec](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/e4e3bec))
|
|
10
|
+
|
|
11
|
+
### ❤️ Contributors
|
|
12
|
+
|
|
13
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
14
|
+
|
|
15
|
+
## v1.6.0
|
|
16
|
+
|
|
17
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.5.1...v1.6.0)
|
|
18
|
+
|
|
19
|
+
### 🚀 Enhancements
|
|
20
|
+
|
|
21
|
+
- Add toFormattedSize function and update PluginProviders interface ([7e8d420](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/7e8d420))
|
|
22
|
+
|
|
23
|
+
### 🩹 Fixes
|
|
24
|
+
|
|
25
|
+
- Implement server bypass rules and enhance session handling in auth module ([c86c70a](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/c86c70a))
|
|
26
|
+
- Update release script to include type checks before publishing ([2895197](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2895197))
|
|
27
|
+
- Enhance documentation and type definitions for server bypass rules and logging utilities ([429d4d7](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/429d4d7))
|
|
28
|
+
|
|
29
|
+
### ❤️ Contributors
|
|
30
|
+
|
|
31
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
32
|
+
|
|
3
33
|
## v1.5.1
|
|
4
34
|
|
|
5
35
|
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.5.0...v1.5.1)
|
package/README.md
CHANGED
|
@@ -144,8 +144,14 @@ All options are nested under `nuxtCore` in `nuxt.config.ts`.
|
|
|
144
144
|
| `loginUri` | `string` | `'/login'` | Path middleware redirects unauthenticated users to |
|
|
145
145
|
| `defaultLoginRedirectUri` | `string` | `'/'` | Default post-login redirect when no intended destination is recorded |
|
|
146
146
|
| `defaultLogoutRedirectUri` | `string` | `'/login'` | Post-logout redirect |
|
|
147
|
-
| `ignoreRegexPatterns` | `RegExp[]` | `[]` | Routes matching these patterns are excluded from middleware in all environments |
|
|
148
|
-
| `ignoreRegexPatternsDev` | `RegExp[]` | `[]` | Routes excluded from middleware in development only |
|
|
147
|
+
| `ignoreRegexPatterns` | `RegExp[]` | `[]` | Routes matching these patterns are excluded from middleware in all environments |
|
|
148
|
+
| `ignoreRegexPatternsDev` | `RegExp[]` | `[]` | Routes excluded from middleware in development only |
|
|
149
|
+
| `appendIgnoreRegexPatterns` | `RegExp[]` | `[]` | Additional routes appended to `ignoreRegexPatterns`, useful for downstream modules |
|
|
150
|
+
| `appendIgnoreRegexPatternsDev` | `RegExp[]` | `[]` | Additional routes appended to `ignoreRegexPatternsDev`, useful for downstream modules |
|
|
151
|
+
| `serverBypassRules` | `{ pattern: string \| RegExp, mode: 'hard' \| 'soft' \| 'none' }[]` | built-ins | Server auth plugin bypass rules; last matching rule wins. Patterns are normalized to serializable regex source strings |
|
|
152
|
+
| `serverBypassRulesDev` | `{ pattern: string \| RegExp, mode: 'hard' \| 'soft' \| 'none' }[]` | `[]` | Development-only server auth plugin bypass rules |
|
|
153
|
+
| `appendServerBypassRules` | `{ pattern: string \| RegExp, mode: 'hard' \| 'soft' \| 'none' }[]` | `[]` | Additional server bypass rules appended after `serverBypassRules` |
|
|
154
|
+
| `appendServerBypassRulesDev` | `{ pattern: string \| RegExp, mode: 'hard' \| 'soft' \| 'none' }[]` | `[]` | Additional development-only server bypass rules |
|
|
149
155
|
|
|
150
156
|
### `logging` options
|
|
151
157
|
|
package/dist/module.d.mts
CHANGED
|
@@ -16,6 +16,13 @@ interface AuthorizedUser {
|
|
|
16
16
|
|
|
17
17
|
type AuthStatus = 'unauthenticated' | 'authenticated'
|
|
18
18
|
|
|
19
|
+
type AuthRequestBypassMode = 'hard' | 'soft' | 'none'
|
|
20
|
+
|
|
21
|
+
type AuthRequestBypassRule = {
|
|
22
|
+
pattern: string | RegExp
|
|
23
|
+
mode: AuthRequestBypassMode
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
type AuthData = {
|
|
20
27
|
status: AuthStatus
|
|
21
28
|
tokenSet?: {
|
|
@@ -226,6 +233,42 @@ type AuthOptions = {
|
|
|
226
233
|
ignoreRegexPatterns: RegExp[]
|
|
227
234
|
|
|
228
235
|
ignoreRegexPatternsDev: RegExp[]
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Server-side auth plugin bypass rules.
|
|
239
|
+
*
|
|
240
|
+
* - `hard`: skips session validation, refresh, userInfo, and cookie writes.
|
|
241
|
+
* - `soft`: validates session and writes cookies, but skips refresh and userInfo side effects.
|
|
242
|
+
* - `none`: applies full auth processing; useful to override an earlier broad rule.
|
|
243
|
+
*
|
|
244
|
+
* Rules are evaluated in order and the last matching rule wins.
|
|
245
|
+
*/
|
|
246
|
+
serverBypassRules: AuthRequestBypassRule[]
|
|
247
|
+
|
|
248
|
+
/** Development-only server-side auth plugin bypass rules. */
|
|
249
|
+
serverBypassRulesDev: AuthRequestBypassRule[]
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Additional ignore patterns appended after the normalized `ignoreRegexPatterns` list.
|
|
253
|
+
* Intended for downstream modules that need to contribute ignored routes without
|
|
254
|
+
* replacing the app-owned base list.
|
|
255
|
+
*/
|
|
256
|
+
appendIgnoreRegexPatterns: RegExp[]
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Additional development-only ignore patterns appended after the normalized
|
|
260
|
+
* `ignoreRegexPatternsDev` list.
|
|
261
|
+
*/
|
|
262
|
+
appendIgnoreRegexPatternsDev: RegExp[]
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Additional server-side bypass rules appended after `serverBypassRules`.
|
|
266
|
+
* Intended for downstream modules that need to contribute system routes.
|
|
267
|
+
*/
|
|
268
|
+
appendServerBypassRules: AuthRequestBypassRule[]
|
|
269
|
+
|
|
270
|
+
/** Additional development-only server-side bypass rules appended after `serverBypassRulesDev`. */
|
|
271
|
+
appendServerBypassRulesDev: AuthRequestBypassRule[]
|
|
229
272
|
}
|
|
230
273
|
|
|
231
274
|
/**
|
|
@@ -304,7 +347,19 @@ type ModulePublicRuntimeConfig = Pick<ModuleOptions, 'defaultLocale' | 'defaultT
|
|
|
304
347
|
|
|
305
348
|
type InputModuleOptions = DeepPartial<ModuleOptions>
|
|
306
349
|
|
|
350
|
+
/**
|
|
351
|
+
* The main entry point for the `@devcoffee/nuxt-core` module.
|
|
352
|
+
*
|
|
353
|
+
* This Nuxt module sets up the core infrastructure for applications, including:
|
|
354
|
+
* - Authentication and session management (authts)
|
|
355
|
+
* - Server and client logging functionality
|
|
356
|
+
* - Global utilities and formatters
|
|
357
|
+
* - Nitro plugins for request handling
|
|
358
|
+
* - Custom Nuxt DevTools integration for inspecting sessions
|
|
359
|
+
*
|
|
360
|
+
* @type {NuxtModule<InputModuleOptions>}
|
|
361
|
+
*/
|
|
307
362
|
declare const _module: NuxtModule<InputModuleOptions>;
|
|
308
363
|
|
|
309
364
|
export { _module as default };
|
|
310
|
-
export type { AuthData, AuthorizedUser, AuthtsMiddlewareMeta, AuthtsModuleOptions, CoreLogInstance, CoreLogLevel, InputModuleOptions, LoggingModuleOptions, LoggingOptions, ModuleOptions, ModulePublicRuntimeConfig, NuxtAuthOptions, NuxtCoreLogging, NuxtSessionContext, NuxtSessionUpdateContext, SessionContext };
|
|
365
|
+
export type { AuthData, AuthRequestBypassMode, AuthRequestBypassRule, AuthorizedUser, AuthtsMiddlewareMeta, AuthtsModuleOptions, CoreLogInstance, CoreLogLevel, InputModuleOptions, LoggingModuleOptions, LoggingOptions, ModuleOptions, ModulePublicRuntimeConfig, NuxtAuthOptions, NuxtCoreLogging, NuxtSessionContext, NuxtSessionUpdateContext, SessionContext };
|
package/dist/module.d.ts
CHANGED
|
@@ -16,6 +16,13 @@ interface AuthorizedUser {
|
|
|
16
16
|
|
|
17
17
|
type AuthStatus = 'unauthenticated' | 'authenticated'
|
|
18
18
|
|
|
19
|
+
type AuthRequestBypassMode = 'hard' | 'soft' | 'none'
|
|
20
|
+
|
|
21
|
+
type AuthRequestBypassRule = {
|
|
22
|
+
pattern: string | RegExp
|
|
23
|
+
mode: AuthRequestBypassMode
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
type AuthData = {
|
|
20
27
|
status: AuthStatus
|
|
21
28
|
tokenSet?: {
|
|
@@ -226,6 +233,42 @@ type AuthOptions = {
|
|
|
226
233
|
ignoreRegexPatterns: RegExp[]
|
|
227
234
|
|
|
228
235
|
ignoreRegexPatternsDev: RegExp[]
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Server-side auth plugin bypass rules.
|
|
239
|
+
*
|
|
240
|
+
* - `hard`: skips session validation, refresh, userInfo, and cookie writes.
|
|
241
|
+
* - `soft`: validates session and writes cookies, but skips refresh and userInfo side effects.
|
|
242
|
+
* - `none`: applies full auth processing; useful to override an earlier broad rule.
|
|
243
|
+
*
|
|
244
|
+
* Rules are evaluated in order and the last matching rule wins.
|
|
245
|
+
*/
|
|
246
|
+
serverBypassRules: AuthRequestBypassRule[]
|
|
247
|
+
|
|
248
|
+
/** Development-only server-side auth plugin bypass rules. */
|
|
249
|
+
serverBypassRulesDev: AuthRequestBypassRule[]
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Additional ignore patterns appended after the normalized `ignoreRegexPatterns` list.
|
|
253
|
+
* Intended for downstream modules that need to contribute ignored routes without
|
|
254
|
+
* replacing the app-owned base list.
|
|
255
|
+
*/
|
|
256
|
+
appendIgnoreRegexPatterns: RegExp[]
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Additional development-only ignore patterns appended after the normalized
|
|
260
|
+
* `ignoreRegexPatternsDev` list.
|
|
261
|
+
*/
|
|
262
|
+
appendIgnoreRegexPatternsDev: RegExp[]
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Additional server-side bypass rules appended after `serverBypassRules`.
|
|
266
|
+
* Intended for downstream modules that need to contribute system routes.
|
|
267
|
+
*/
|
|
268
|
+
appendServerBypassRules: AuthRequestBypassRule[]
|
|
269
|
+
|
|
270
|
+
/** Additional development-only server-side bypass rules appended after `serverBypassRulesDev`. */
|
|
271
|
+
appendServerBypassRulesDev: AuthRequestBypassRule[]
|
|
229
272
|
}
|
|
230
273
|
|
|
231
274
|
/**
|
|
@@ -304,7 +347,19 @@ type ModulePublicRuntimeConfig = Pick<ModuleOptions, 'defaultLocale' | 'defaultT
|
|
|
304
347
|
|
|
305
348
|
type InputModuleOptions = DeepPartial<ModuleOptions>
|
|
306
349
|
|
|
350
|
+
/**
|
|
351
|
+
* The main entry point for the `@devcoffee/nuxt-core` module.
|
|
352
|
+
*
|
|
353
|
+
* This Nuxt module sets up the core infrastructure for applications, including:
|
|
354
|
+
* - Authentication and session management (authts)
|
|
355
|
+
* - Server and client logging functionality
|
|
356
|
+
* - Global utilities and formatters
|
|
357
|
+
* - Nitro plugins for request handling
|
|
358
|
+
* - Custom Nuxt DevTools integration for inspecting sessions
|
|
359
|
+
*
|
|
360
|
+
* @type {NuxtModule<InputModuleOptions>}
|
|
361
|
+
*/
|
|
307
362
|
declare const _module: NuxtModule<InputModuleOptions>;
|
|
308
363
|
|
|
309
364
|
export { _module as default };
|
|
310
|
-
export type { AuthData, AuthorizedUser, AuthtsMiddlewareMeta, AuthtsModuleOptions, CoreLogInstance, CoreLogLevel, InputModuleOptions, LoggingModuleOptions, LoggingOptions, ModuleOptions, ModulePublicRuntimeConfig, NuxtAuthOptions, NuxtCoreLogging, NuxtSessionContext, NuxtSessionUpdateContext, SessionContext };
|
|
365
|
+
export type { AuthData, AuthRequestBypassMode, AuthRequestBypassRule, AuthorizedUser, AuthtsMiddlewareMeta, AuthtsModuleOptions, CoreLogInstance, CoreLogLevel, InputModuleOptions, LoggingModuleOptions, LoggingOptions, ModuleOptions, ModulePublicRuntimeConfig, NuxtAuthOptions, NuxtCoreLogging, NuxtSessionContext, NuxtSessionUpdateContext, SessionContext };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -2,11 +2,17 @@ import { addCustomTab } from '@nuxt/devtools-kit';
|
|
|
2
2
|
import { defineNuxtModule, useLogger, createResolver, addTemplate, addServerImports, addServerImportsDir, addServerPlugin, addImportsDir, addPlugin, addRouteMiddleware, addServerHandler } from '@nuxt/kit';
|
|
3
3
|
import { deepMerge, pick } from '../dist/runtime/utils.js';
|
|
4
4
|
|
|
5
|
-
const version = "1.
|
|
5
|
+
const version = "1.6.1";
|
|
6
6
|
|
|
7
7
|
const defaultLocale = "vi-VN";
|
|
8
8
|
const defaultLanguage = "vi";
|
|
9
9
|
const defaultTimeZone = "Asia/Ho_Chi_Minh";
|
|
10
|
+
const builtInServerBypassRules = [
|
|
11
|
+
{ pattern: "^/__devcoffee_core_session_devtools__$", mode: "hard" },
|
|
12
|
+
{ pattern: "^/_i18n/", mode: "hard" },
|
|
13
|
+
{ pattern: "^/_nuxt/", mode: "hard" },
|
|
14
|
+
{ pattern: "^/__nuxt", mode: "soft" }
|
|
15
|
+
];
|
|
10
16
|
const loggingDefaults = {
|
|
11
17
|
server: {
|
|
12
18
|
tag: "server",
|
|
@@ -19,7 +25,8 @@ const loggingDefaults = {
|
|
|
19
25
|
client: {
|
|
20
26
|
tag: "app-client",
|
|
21
27
|
level: 2
|
|
22
|
-
}
|
|
28
|
+
},
|
|
29
|
+
loggers: {}
|
|
23
30
|
};
|
|
24
31
|
const authtsDefaults = {
|
|
25
32
|
enabled: true,
|
|
@@ -74,11 +81,37 @@ const authtsDefaults = {
|
|
|
74
81
|
timezone: defaultTimeZone
|
|
75
82
|
},
|
|
76
83
|
ignoreRegexPatterns: [],
|
|
77
|
-
ignoreRegexPatternsDev: []
|
|
84
|
+
ignoreRegexPatternsDev: [],
|
|
85
|
+
serverBypassRules: [],
|
|
86
|
+
serverBypassRulesDev: [],
|
|
87
|
+
appendIgnoreRegexPatterns: [],
|
|
88
|
+
appendIgnoreRegexPatternsDev: [],
|
|
89
|
+
appendServerBypassRules: [],
|
|
90
|
+
appendServerBypassRulesDev: []
|
|
78
91
|
}
|
|
79
92
|
};
|
|
93
|
+
function collectAuthIgnorePatterns(inputOpts, key) {
|
|
94
|
+
return inputOpts.flatMap((opts) => (opts.authts?.auth?.[key] || []).filter(isRegExp));
|
|
95
|
+
}
|
|
96
|
+
function collectAuthServerBypassRules(inputOpts, key) {
|
|
97
|
+
return inputOpts.flatMap((opts) => (opts.authts?.auth?.[key] || []).filter(isAuthRequestBypassRule));
|
|
98
|
+
}
|
|
99
|
+
function isRegExp(value) {
|
|
100
|
+
return value instanceof RegExp;
|
|
101
|
+
}
|
|
102
|
+
function isAuthRequestBypassRule(value) {
|
|
103
|
+
if (!value || typeof value !== "object") return false;
|
|
104
|
+
const rule = value;
|
|
105
|
+
return (isRegExp(rule.pattern) || typeof rule.pattern === "string") && ["hard", "soft", "none"].includes(String(rule.mode));
|
|
106
|
+
}
|
|
107
|
+
function normalizeAuthRequestBypassRule(rule) {
|
|
108
|
+
return {
|
|
109
|
+
pattern: typeof rule.pattern === "string" ? rule.pattern : rule.pattern.source,
|
|
110
|
+
mode: rule.mode
|
|
111
|
+
};
|
|
112
|
+
}
|
|
80
113
|
function normalizedModuleOptions(...inputOpts) {
|
|
81
|
-
|
|
114
|
+
const mergedOptions = deepMerge(
|
|
82
115
|
{
|
|
83
116
|
defaultLocale,
|
|
84
117
|
defaultLanguage,
|
|
@@ -88,6 +121,32 @@ function normalizedModuleOptions(...inputOpts) {
|
|
|
88
121
|
},
|
|
89
122
|
...inputOpts
|
|
90
123
|
);
|
|
124
|
+
const appendIgnoreRegexPatterns = collectAuthIgnorePatterns(inputOpts, "appendIgnoreRegexPatterns");
|
|
125
|
+
const appendIgnoreRegexPatternsDev = collectAuthIgnorePatterns(inputOpts, "appendIgnoreRegexPatternsDev");
|
|
126
|
+
const appendServerBypassRules = collectAuthServerBypassRules(inputOpts, "appendServerBypassRules");
|
|
127
|
+
const appendServerBypassRulesDev = collectAuthServerBypassRules(inputOpts, "appendServerBypassRulesDev");
|
|
128
|
+
mergedOptions.authts.auth.appendIgnoreRegexPatterns = appendIgnoreRegexPatterns;
|
|
129
|
+
mergedOptions.authts.auth.appendIgnoreRegexPatternsDev = appendIgnoreRegexPatternsDev;
|
|
130
|
+
mergedOptions.authts.auth.appendServerBypassRules = appendServerBypassRules.map(normalizeAuthRequestBypassRule);
|
|
131
|
+
mergedOptions.authts.auth.appendServerBypassRulesDev = appendServerBypassRulesDev.map(normalizeAuthRequestBypassRule);
|
|
132
|
+
mergedOptions.authts.auth.ignoreRegexPatterns = [
|
|
133
|
+
...mergedOptions.authts.auth.ignoreRegexPatterns || [],
|
|
134
|
+
...appendIgnoreRegexPatterns
|
|
135
|
+
];
|
|
136
|
+
mergedOptions.authts.auth.ignoreRegexPatternsDev = [
|
|
137
|
+
...mergedOptions.authts.auth.ignoreRegexPatternsDev || [],
|
|
138
|
+
...appendIgnoreRegexPatternsDev
|
|
139
|
+
];
|
|
140
|
+
mergedOptions.authts.auth.serverBypassRules = [
|
|
141
|
+
...builtInServerBypassRules,
|
|
142
|
+
...mergedOptions.authts.auth.serverBypassRules || [],
|
|
143
|
+
...appendServerBypassRules
|
|
144
|
+
].map(normalizeAuthRequestBypassRule);
|
|
145
|
+
mergedOptions.authts.auth.serverBypassRulesDev = [
|
|
146
|
+
...mergedOptions.authts.auth.serverBypassRulesDev || [],
|
|
147
|
+
...appendServerBypassRulesDev
|
|
148
|
+
].map(normalizeAuthRequestBypassRule);
|
|
149
|
+
return mergedOptions;
|
|
91
150
|
}
|
|
92
151
|
function normalizePublicRuntimeConfig(inputOpts) {
|
|
93
152
|
const { enabled } = inputOpts.authts;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import type { CoreLogInstance, CoreLogLevel } from '#app';
|
|
2
|
+
/**
|
|
3
|
+
* 📝 Provides access to the core logging instance.
|
|
4
|
+
*
|
|
5
|
+
* Retrieves a Nuxt logger configured with the given options, falling back to
|
|
6
|
+
* default NuxtCore logging settings.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} [opts] - Optional logger configuration.
|
|
9
|
+
* @param {string} [opts.tag] - A specific tag to apply to the logger.
|
|
10
|
+
* @param {CoreLogLevel} [opts.level] - Override the default log level.
|
|
11
|
+
* @returns {CoreLogInstance} A configured logger instance.
|
|
12
|
+
* @since 1.0.0
|
|
13
|
+
*/
|
|
2
14
|
export default function useLogger(opts?: {
|
|
3
15
|
tag?: string;
|
|
4
16
|
level?: CoreLogLevel;
|
|
@@ -1,20 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for padding a number or string.
|
|
3
|
+
*/
|
|
1
4
|
type PadOptions = {
|
|
5
|
+
/** A prefix to apply before the padded string. */
|
|
2
6
|
prefix: string & {
|
|
3
7
|
__uppercaseBrand?: never;
|
|
4
8
|
};
|
|
9
|
+
/** The separator to insert between the prefix and the padded string. */
|
|
5
10
|
separator: string;
|
|
11
|
+
/** The total target length to pad to. */
|
|
6
12
|
length: number;
|
|
13
|
+
/** The character to use for padding. */
|
|
7
14
|
padChar: string;
|
|
8
15
|
};
|
|
16
|
+
/** Formats a number with specific padding and prefix options. */
|
|
9
17
|
type ToPad = (num: number, opts?: Partial<PadOptions>) => string;
|
|
18
|
+
/** Formats a raw number string into a US phone number format `(XXX) XXX-XXXX`. */
|
|
10
19
|
type AsPhoneText = (numStr: string) => string;
|
|
20
|
+
/** Converts a byte value into a formatted human-readable file size. */
|
|
21
|
+
type ToFormattedSize = (numStr?: Nullable<string> | Nullable<number>) => string;
|
|
22
|
+
/** Formats a Date object into a localized date string. */
|
|
11
23
|
type AsDateString = (date: Date) => string;
|
|
24
|
+
/** Formats a Date object into a localized time string. */
|
|
12
25
|
type AsTimeString = (date: Date, seconds?: boolean) => string;
|
|
26
|
+
/** Formats a Date object into a localized date and time string. */
|
|
13
27
|
type AsDateTimeString = (date: Date, seconds?: boolean) => string;
|
|
28
|
+
/** Formats a Date object into a relative time string (e.g. "2 days ago"). */
|
|
14
29
|
type AsRelativeTimeString = (date: Date) => string;
|
|
30
|
+
/** Defines the formatter functions provided to the Nuxt application context. */
|
|
15
31
|
type PluginProviders = {
|
|
16
32
|
toPad: ToPad;
|
|
17
33
|
asPhoneText: AsPhoneText;
|
|
34
|
+
toFormattedSize: ToFormattedSize;
|
|
18
35
|
asDateString: AsDateString;
|
|
19
36
|
asTimeString: AsTimeString;
|
|
20
37
|
asDateTimeString: AsDateTimeString;
|
|
@@ -25,5 +42,18 @@ declare module 'vue' {
|
|
|
25
42
|
$formatters: PluginProviders;
|
|
26
43
|
}
|
|
27
44
|
}
|
|
45
|
+
declare module '#app' {
|
|
46
|
+
interface NuxtApp {
|
|
47
|
+
$formatters: PluginProviders;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 🧩 Nuxt plugin for global data formatting utilities.
|
|
52
|
+
*
|
|
53
|
+
* Provides helpers for formatting numbers, dates, times, filesizes, and phone numbers
|
|
54
|
+
* across the application, actively aware of the user's current locale and timezone.
|
|
55
|
+
*
|
|
56
|
+
* @since 1.0.0
|
|
57
|
+
*/
|
|
28
58
|
declare const _default: any;
|
|
29
59
|
export default _default;
|
|
@@ -42,6 +42,15 @@ export default defineNuxtPlugin((_nuxtApp) => {
|
|
|
42
42
|
const digits = numStr.replace(/\D/g, "");
|
|
43
43
|
return digits.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");
|
|
44
44
|
}
|
|
45
|
+
function toFormattedSize(numStr) {
|
|
46
|
+
if (!numStr) return "0B";
|
|
47
|
+
const bytes = typeof numStr == "number" ? numStr : parseInt(numStr);
|
|
48
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
49
|
+
const index = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
|
|
50
|
+
const value = bytes / 1024 ** index;
|
|
51
|
+
const rounded = value >= 10 || index === 0 ? Math.round(value) : Number(value.toFixed(1));
|
|
52
|
+
return `${rounded}${units[index]}`;
|
|
53
|
+
}
|
|
45
54
|
function asDateString(date) {
|
|
46
55
|
logger.debug(`Format date with locale ='${currentLocale.value}'`);
|
|
47
56
|
return new Intl.DateTimeFormat(currentLocale.value).format(date);
|
|
@@ -99,12 +108,17 @@ export default defineNuxtPlugin((_nuxtApp) => {
|
|
|
99
108
|
}
|
|
100
109
|
return asDateTimeString(date, true);
|
|
101
110
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
return {
|
|
112
|
+
provide: {
|
|
113
|
+
formatters: {
|
|
114
|
+
toPad,
|
|
115
|
+
toFormattedSize,
|
|
116
|
+
asPhoneText,
|
|
117
|
+
asDateString,
|
|
118
|
+
asTimeString,
|
|
119
|
+
asDateTimeString,
|
|
120
|
+
asRelativeTimeString
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
110
124
|
});
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the current active locale configuration.
|
|
3
|
+
*/
|
|
1
4
|
type LocaleState = {
|
|
2
5
|
locale: string;
|
|
3
6
|
language: string;
|
|
4
7
|
timeZone: string;
|
|
5
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* The provider shape for locale settings exposed to the application context.
|
|
11
|
+
*/
|
|
6
12
|
type LocaleProvider = LocaleState;
|
|
7
13
|
/**
|
|
8
14
|
* 🧩 Nuxt plugin for locale utilities.
|
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a random short hash without hyphens.
|
|
3
|
+
*
|
|
4
|
+
* @param {number} [length=8] - The desired length of the resulting hash.
|
|
5
|
+
* @returns {string} A random string of the specified length.
|
|
6
|
+
* @since 1.0.0
|
|
7
|
+
*/
|
|
1
8
|
export declare function randomShortHash(length?: number): string;
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { type ConsolaInstance, type LogLevel } from 'consola';
|
|
2
2
|
import type { H3Event } from 'h3';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves or initializes the server-side logger instance.
|
|
5
|
+
*
|
|
6
|
+
* Uses the Nuxt runtime configuration to set up a `consola` logger with the appropriate
|
|
7
|
+
* tags and log levels. If an H3 event is provided, it will be used to fetch the current event's runtime config.
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} [options] - Optional logger configuration.
|
|
10
|
+
* @param {H3Event} [options.event] - The current H3 event, used to fetch runtime config context.
|
|
11
|
+
* @param {string} [options.tag] - A specific tag to apply to the logger.
|
|
12
|
+
* @param {LogLevel} [options.level] - Override the default log level.
|
|
13
|
+
* @returns {ConsolaInstance} A configured Consola logger instance.
|
|
14
|
+
*/
|
|
3
15
|
export default function useServerLogger(options?: Partial<{
|
|
4
16
|
event?: H3Event;
|
|
5
17
|
tag?: string;
|
|
@@ -77,6 +77,15 @@ export declare function validateSession(sessionCookieId: string | undefined, opt
|
|
|
77
77
|
* @since 1.0.0
|
|
78
78
|
*/
|
|
79
79
|
export declare function updateSession(sessionId: string, input: DeepPartial<Omit<SessionContext, 'id' | 'expiresAt' | 'issuedAt'>>, opts: SessionCreateOptions): Promise<SessionContext>;
|
|
80
|
+
/**
|
|
81
|
+
* Renews an existing session by destroying it and creating a fresh unauthenticated session
|
|
82
|
+
* with a new session ID and updated expiration time.
|
|
83
|
+
*
|
|
84
|
+
* @param sessionId - The ID of the session to renew.
|
|
85
|
+
* @param opts - Session creation options including storage name, prefix, and expiration time.
|
|
86
|
+
* @returns A promise that resolves to the newly created {@link SessionContext}.
|
|
87
|
+
* @since 1.0.0
|
|
88
|
+
*/
|
|
80
89
|
export declare function renewSession(sessionId: string, opts: SessionCreateOptions): Promise<SessionContext>;
|
|
81
90
|
/**
|
|
82
91
|
* Delete a session from storage by ID.
|
|
@@ -180,6 +189,14 @@ export declare function authorizationCodeGrant(authorizeParams: {
|
|
|
180
189
|
redirectUri: string;
|
|
181
190
|
usePkce: boolean;
|
|
182
191
|
}): Promise<any>;
|
|
192
|
+
/**
|
|
193
|
+
* Constructs a normalized token set from an OpenID Connect token endpoint response.
|
|
194
|
+
* Ensures the token type is properly capitalized.
|
|
195
|
+
*
|
|
196
|
+
* @param input - The raw token endpoint response.
|
|
197
|
+
* @returns A normalized token set containing access, id, and refresh tokens along with expiry.
|
|
198
|
+
* @since 1.0.0
|
|
199
|
+
*/
|
|
183
200
|
export declare function constructTokenSet(input: TokenEndpointResponse): {
|
|
184
201
|
tokenType: string;
|
|
185
202
|
idToken: any;
|
|
@@ -188,6 +205,15 @@ export declare function constructTokenSet(input: TokenEndpointResponse): {
|
|
|
188
205
|
scopes: any;
|
|
189
206
|
expiresAt: number;
|
|
190
207
|
};
|
|
208
|
+
/**
|
|
209
|
+
* Checks if the session's access token is expired or expiring soon, and refreshes it if necessary.
|
|
210
|
+
* Implements a distributed lock to prevent concurrent refresh attempts for the same session.
|
|
211
|
+
*
|
|
212
|
+
* @param session - The current session context.
|
|
213
|
+
* @param opts - Configuration options for the refresh token grant, caching, and locking.
|
|
214
|
+
* @returns A partial session update containing the new authentication status and user, or empty object if no refresh occurred.
|
|
215
|
+
* @since 1.0.0
|
|
216
|
+
*/
|
|
191
217
|
export declare function refreshTokenIfNeeded(session: SessionContext, opts: {
|
|
192
218
|
wellKnownUrl: string;
|
|
193
219
|
cache: {
|
|
@@ -208,7 +234,7 @@ export declare function refreshTokenIfNeeded(session: SessionContext, opts: {
|
|
|
208
234
|
/**
|
|
209
235
|
* Fetch user profile information from the OpenID Provider using the access token.
|
|
210
236
|
*
|
|
211
|
-
* @param
|
|
237
|
+
* @param accessToken - The access token used to authenticate the request to the userinfo endpoint.
|
|
212
238
|
* @param sub - The subject identifier of the authenticated user.
|
|
213
239
|
* @param opts - OpenID discovery and client configuration options.
|
|
214
240
|
* @returns Simplified user information (id, sub, email, first/last name).
|
|
@@ -217,6 +243,15 @@ export declare function refreshTokenIfNeeded(session: SessionContext, opts: {
|
|
|
217
243
|
export declare function fetchUserInfo(accessToken: string, sub: string, opts: OpenIdDiscoveryOptions & {
|
|
218
244
|
wellKnownUrl: string;
|
|
219
245
|
}): Promise<any>;
|
|
246
|
+
/**
|
|
247
|
+
* Revoke an array of tokens (e.g., access and refresh tokens) at the OpenID Provider.
|
|
248
|
+
* Skips empty or undefined tokens.
|
|
249
|
+
*
|
|
250
|
+
* @param tokens - An array of token strings to revoke.
|
|
251
|
+
* @param opts - OpenID discovery and client configuration options.
|
|
252
|
+
* @returns A promise resolving to an array of settlement results for each revocation request.
|
|
253
|
+
* @since 1.0.0
|
|
254
|
+
*/
|
|
220
255
|
export declare function revokeTokens(tokens: string[], opts: OpenIdDiscoveryOptions & {
|
|
221
256
|
wellKnownUrl: string;
|
|
222
257
|
}): Promise<PromiseSettledResult<any>[]>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: any;
|
|
2
2
|
export default _default;
|
|
@@ -1,4 +1,281 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { eventHandler, getCookie, getQuery, useRuntimeConfig } from "#devcoffee-core/server/adapters/http";
|
|
2
|
+
import { getSessionData } from "#devcoffee-core/server/adapters/storage";
|
|
3
|
+
import { isValidSessionId, verifySessionId } from "#devcoffee-core/server/core/crypto";
|
|
4
|
+
function getSessionStorageKey(storagePrefix, sessionId) {
|
|
5
|
+
return storagePrefix ? `${storagePrefix}:${sessionId}` : sessionId;
|
|
6
|
+
}
|
|
7
|
+
function isEncryptedTokenSet(tokenSet) {
|
|
8
|
+
return Boolean(tokenSet && "encrypted" in tokenSet && tokenSet.encrypted === true);
|
|
9
|
+
}
|
|
10
|
+
function sanitizeSession(session) {
|
|
11
|
+
const tokenSet = session.auth?.tokenSet;
|
|
12
|
+
const tokenSetEncrypted = isEncryptedTokenSet(tokenSet);
|
|
13
|
+
const tokenSetPresent = Boolean(tokenSet);
|
|
14
|
+
const tokenExpiresAt = tokenSet && !tokenSetEncrypted && "expiresAt" in tokenSet ? tokenSet.expiresAt : void 0;
|
|
15
|
+
return {
|
|
16
|
+
...session,
|
|
17
|
+
auth: {
|
|
18
|
+
status: session.auth?.status || "unauthenticated",
|
|
19
|
+
tokenSet: {
|
|
20
|
+
encrypted: tokenSetEncrypted,
|
|
21
|
+
present: tokenSetPresent,
|
|
22
|
+
tokenType: tokenSet && !tokenSetEncrypted && "tokenType" in tokenSet ? tokenSet.tokenType : void 0,
|
|
23
|
+
scopes: tokenSet && !tokenSetEncrypted && "scopes" in tokenSet ? tokenSet.scopes : void 0,
|
|
24
|
+
expiresAt: tokenExpiresAt,
|
|
25
|
+
expiresInMs: typeof tokenExpiresAt === "number" ? tokenExpiresAt - Date.now() : void 0
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function readSessionSnapshot(event) {
|
|
31
|
+
const {
|
|
32
|
+
sessions: {
|
|
33
|
+
secret = "",
|
|
34
|
+
storage: { name: storageName, prefix: storagePrefix },
|
|
35
|
+
names: { sessionId: cookieName }
|
|
36
|
+
}
|
|
37
|
+
} = useRuntimeConfig(event).nuxtCore.authts;
|
|
38
|
+
const cookieValue = getCookie(event, cookieName);
|
|
39
|
+
const sessionId = verifySessionId(cookieValue, secret);
|
|
40
|
+
if (!cookieValue) {
|
|
41
|
+
return createSnapshot("missing-cookie", null, null, null);
|
|
42
|
+
}
|
|
43
|
+
if (!sessionId || !isValidSessionId(sessionId)) {
|
|
44
|
+
return createSnapshot("invalid-cookie", null, null, null);
|
|
45
|
+
}
|
|
46
|
+
const storageKey = getSessionStorageKey(storagePrefix, sessionId);
|
|
47
|
+
const session = await getSessionData(storageName, storageKey);
|
|
48
|
+
if (!session) {
|
|
49
|
+
return createSnapshot("missing-session", sessionId, storageKey, null);
|
|
50
|
+
}
|
|
51
|
+
const status = session.expiresAt <= Date.now() ? "expired" : "active";
|
|
52
|
+
return createSnapshot(status, sessionId, storageKey, sanitizeSession(session));
|
|
53
|
+
}
|
|
54
|
+
function createSnapshot(status, sessionId, storageKey, session) {
|
|
55
|
+
return {
|
|
56
|
+
meta: {
|
|
57
|
+
readOnly: true,
|
|
58
|
+
loadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
59
|
+
status,
|
|
60
|
+
sessionId,
|
|
61
|
+
storageKey
|
|
62
|
+
},
|
|
63
|
+
session
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function renderSessionInspector() {
|
|
67
|
+
return `<!doctype html>
|
|
68
|
+
<html lang="en">
|
|
69
|
+
<head>
|
|
70
|
+
<meta charset="utf-8">
|
|
71
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
72
|
+
<title>Devcoffee Session</title>
|
|
73
|
+
<style>
|
|
74
|
+
:root {
|
|
75
|
+
color-scheme: light dark;
|
|
76
|
+
--bg: #f7f8fb;
|
|
77
|
+
--panel: #ffffff;
|
|
78
|
+
--text: #17202a;
|
|
79
|
+
--muted: #637083;
|
|
80
|
+
--border: #d9dee8;
|
|
81
|
+
--accent: #0f766e;
|
|
82
|
+
--accent-strong: #0d5f59;
|
|
83
|
+
--code: #101828;
|
|
84
|
+
--code-bg: #eef2f7;
|
|
85
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@media (prefers-color-scheme: dark) {
|
|
89
|
+
:root {
|
|
90
|
+
--bg: #111827;
|
|
91
|
+
--panel: #182233;
|
|
92
|
+
--text: #ecf1f8;
|
|
93
|
+
--muted: #aab6c7;
|
|
94
|
+
--border: #314057;
|
|
95
|
+
--accent: #2dd4bf;
|
|
96
|
+
--accent-strong: #5eead4;
|
|
97
|
+
--code: #e8eef8;
|
|
98
|
+
--code-bg: #0f172a;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
* { box-sizing: border-box; }
|
|
103
|
+
body {
|
|
104
|
+
margin: 0;
|
|
105
|
+
min-height: 100vh;
|
|
106
|
+
background: var(--bg);
|
|
107
|
+
color: var(--text);
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
}
|
|
110
|
+
main {
|
|
111
|
+
width: min(980px, calc(100vw - 32px));
|
|
112
|
+
margin: 0 auto;
|
|
113
|
+
padding: 24px 0;
|
|
114
|
+
}
|
|
115
|
+
header {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: space-between;
|
|
119
|
+
gap: 16px;
|
|
120
|
+
margin-bottom: 16px;
|
|
121
|
+
}
|
|
122
|
+
h1 {
|
|
123
|
+
margin: 0;
|
|
124
|
+
font-size: 20px;
|
|
125
|
+
font-weight: 700;
|
|
126
|
+
letter-spacing: 0;
|
|
127
|
+
}
|
|
128
|
+
button {
|
|
129
|
+
border: 1px solid var(--accent);
|
|
130
|
+
border-radius: 6px;
|
|
131
|
+
background: var(--accent);
|
|
132
|
+
color: #ffffff;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
font: inherit;
|
|
135
|
+
font-weight: 700;
|
|
136
|
+
min-height: 36px;
|
|
137
|
+
padding: 0 14px;
|
|
138
|
+
}
|
|
139
|
+
button:hover { background: var(--accent-strong); }
|
|
140
|
+
button:disabled {
|
|
141
|
+
cursor: wait;
|
|
142
|
+
opacity: .7;
|
|
143
|
+
}
|
|
144
|
+
.panel {
|
|
145
|
+
background: var(--panel);
|
|
146
|
+
border: 1px solid var(--border);
|
|
147
|
+
border-radius: 8px;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
}
|
|
150
|
+
.summary {
|
|
151
|
+
display: grid;
|
|
152
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
153
|
+
gap: 0;
|
|
154
|
+
border-bottom: 1px solid var(--border);
|
|
155
|
+
}
|
|
156
|
+
.item {
|
|
157
|
+
min-width: 0;
|
|
158
|
+
padding: 14px 16px;
|
|
159
|
+
border-right: 1px solid var(--border);
|
|
160
|
+
}
|
|
161
|
+
.item:last-child { border-right: 0; }
|
|
162
|
+
.label {
|
|
163
|
+
color: var(--muted);
|
|
164
|
+
font-size: 12px;
|
|
165
|
+
font-weight: 700;
|
|
166
|
+
margin-bottom: 6px;
|
|
167
|
+
text-transform: uppercase;
|
|
168
|
+
}
|
|
169
|
+
.value {
|
|
170
|
+
overflow-wrap: anywhere;
|
|
171
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
172
|
+
font-size: 13px;
|
|
173
|
+
}
|
|
174
|
+
pre {
|
|
175
|
+
margin: 0;
|
|
176
|
+
min-height: 320px;
|
|
177
|
+
overflow: auto;
|
|
178
|
+
padding: 16px;
|
|
179
|
+
background: var(--code-bg);
|
|
180
|
+
color: var(--code);
|
|
181
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
182
|
+
font-size: 12px;
|
|
183
|
+
line-height: 1.55;
|
|
184
|
+
white-space: pre-wrap;
|
|
185
|
+
word-break: break-word;
|
|
186
|
+
}
|
|
187
|
+
.error {
|
|
188
|
+
color: #b42318;
|
|
189
|
+
padding: 16px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@media (max-width: 720px) {
|
|
193
|
+
header { align-items: flex-start; flex-direction: column; }
|
|
194
|
+
button { width: 100%; }
|
|
195
|
+
.summary { grid-template-columns: 1fr; }
|
|
196
|
+
.item { border-bottom: 1px solid var(--border); border-right: 0; }
|
|
197
|
+
.item:last-child { border-bottom: 0; }
|
|
198
|
+
}
|
|
199
|
+
</style>
|
|
200
|
+
</head>
|
|
201
|
+
<body>
|
|
202
|
+
<main>
|
|
203
|
+
<header>
|
|
204
|
+
<h1>Devcoffee Session</h1>
|
|
205
|
+
<button id="refresh" type="button">Refresh</button>
|
|
206
|
+
</header>
|
|
207
|
+
<section class="panel">
|
|
208
|
+
<div class="summary">
|
|
209
|
+
<div class="item">
|
|
210
|
+
<div class="label">Status</div>
|
|
211
|
+
<div class="value" id="status">loading</div>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="item">
|
|
214
|
+
<div class="label">Authenticated</div>
|
|
215
|
+
<div class="value" id="authenticated">unknown</div>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="item">
|
|
218
|
+
<div class="label">User</div>
|
|
219
|
+
<div class="value" id="user">unknown</div>
|
|
220
|
+
</div>
|
|
221
|
+
<div class="item">
|
|
222
|
+
<div class="label">Loaded</div>
|
|
223
|
+
<div class="value" id="loaded">pending</div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
<pre id="payload">{}</pre>
|
|
227
|
+
<div class="error" id="error" hidden></div>
|
|
228
|
+
</section>
|
|
229
|
+
</main>
|
|
230
|
+
<script>
|
|
231
|
+
const refreshButton = document.getElementById('refresh')
|
|
232
|
+
const statusNode = document.getElementById('status')
|
|
233
|
+
const authenticatedNode = document.getElementById('authenticated')
|
|
234
|
+
const userNode = document.getElementById('user')
|
|
235
|
+
const loadedNode = document.getElementById('loaded')
|
|
236
|
+
const payloadNode = document.getElementById('payload')
|
|
237
|
+
const errorNode = document.getElementById('error')
|
|
238
|
+
|
|
239
|
+
async function loadSession() {
|
|
240
|
+
refreshButton.disabled = true
|
|
241
|
+
errorNode.hidden = true
|
|
242
|
+
statusNode.textContent = 'loading'
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const response = await fetch('?format=json&ts=' + Date.now(), {
|
|
246
|
+
credentials: 'same-origin',
|
|
247
|
+
headers: { Accept: 'application/json' },
|
|
248
|
+
})
|
|
249
|
+
if (!response.ok) throw new Error('Request failed: ' + response.status)
|
|
250
|
+
|
|
251
|
+
const snapshot = await response.json()
|
|
252
|
+
const session = snapshot.session
|
|
253
|
+
statusNode.textContent = snapshot.meta.status
|
|
254
|
+
authenticatedNode.textContent = String(session?.auth?.status === 'authenticated')
|
|
255
|
+
userNode.textContent = session?.user?.email || session?.user?.id || 'none'
|
|
256
|
+
loadedNode.textContent = new Date(snapshot.meta.loadedAt).toLocaleTimeString()
|
|
257
|
+
payloadNode.textContent = JSON.stringify(snapshot, null, 2)
|
|
258
|
+
} catch (error) {
|
|
259
|
+
errorNode.hidden = false
|
|
260
|
+
errorNode.textContent = error instanceof Error ? error.message : String(error)
|
|
261
|
+
statusNode.textContent = 'error'
|
|
262
|
+
} finally {
|
|
263
|
+
refreshButton.disabled = false
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
refreshButton.addEventListener('click', loadSession)
|
|
268
|
+
loadSession()
|
|
269
|
+
<\/script>
|
|
270
|
+
</body>
|
|
271
|
+
</html>`;
|
|
272
|
+
}
|
|
273
|
+
export default eventHandler(async (event) => {
|
|
274
|
+
const query = getQuery(event);
|
|
275
|
+
if (query.format === "json") {
|
|
276
|
+
event.node.res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
277
|
+
return await readSessionSnapshot(event);
|
|
278
|
+
}
|
|
279
|
+
event.node.res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
280
|
+
return renderSessionInspector();
|
|
4
281
|
});
|
|
@@ -7,23 +7,42 @@ import {
|
|
|
7
7
|
writeSessionCookie
|
|
8
8
|
} from "#devcoffee-core/server/core/helpers";
|
|
9
9
|
import { useNitroApp, useStorage } from "nitropack/runtime";
|
|
10
|
-
|
|
10
|
+
const fallbackServerBypassRules = [
|
|
11
|
+
{ pattern: "^/__devcoffee_core_session_devtools__$", mode: "hard" },
|
|
12
|
+
{ pattern: "^/_i18n/", mode: "hard" },
|
|
13
|
+
{ pattern: "^/_nuxt/", mode: "hard" },
|
|
14
|
+
{ pattern: "^/__nuxt", mode: "soft" }
|
|
15
|
+
];
|
|
16
|
+
function testBypassRule(rule, pathname) {
|
|
17
|
+
const pattern = typeof rule.pattern === "string" ? new RegExp(rule.pattern) : rule.pattern;
|
|
18
|
+
pattern.lastIndex = 0;
|
|
19
|
+
return pattern.test(pathname);
|
|
20
|
+
}
|
|
21
|
+
function getAuthRequestBypass(event, opts = {}) {
|
|
11
22
|
const path = event.path || "";
|
|
23
|
+
const pathname = path.split("?")[0] || "";
|
|
12
24
|
const i18nHeader = getRequestHeaders(event)["x-nuxt-i18n"];
|
|
13
|
-
if (i18nHeader?.toLowerCase() === "internal"
|
|
25
|
+
if (i18nHeader?.toLowerCase() === "internal") {
|
|
14
26
|
return "hard";
|
|
15
27
|
}
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
const serverBypassRules = opts.serverBypassRules?.length ? opts.serverBypassRules : fallbackServerBypassRules;
|
|
29
|
+
const serverBypassRulesDev = opts.serverBypassRulesDev || [];
|
|
30
|
+
let mode = "none";
|
|
31
|
+
for (const rule of serverBypassRules) {
|
|
32
|
+
if (testBypassRule(rule, pathname)) mode = rule.mode;
|
|
33
|
+
}
|
|
34
|
+
if (import.meta.dev) {
|
|
35
|
+
for (const rule of serverBypassRulesDev) {
|
|
36
|
+
if (testBypassRule(rule, pathname)) mode = rule.mode;
|
|
37
|
+
}
|
|
18
38
|
}
|
|
19
|
-
return
|
|
39
|
+
return mode;
|
|
20
40
|
}
|
|
21
41
|
export default defineNitroPlugin((nitroApp) => {
|
|
22
42
|
nitroApp.hooks.hook("request", async (event) => {
|
|
23
|
-
const authRequestBypass = getAuthRequestBypass(event);
|
|
24
|
-
if (authRequestBypass === "hard") return;
|
|
25
43
|
const {
|
|
26
44
|
enabled: authtsEnabled,
|
|
45
|
+
auth: { serverBypassRules, serverBypassRulesDev } = {},
|
|
27
46
|
openid: {
|
|
28
47
|
wellKnownUrl,
|
|
29
48
|
cache,
|
|
@@ -41,6 +60,8 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
41
60
|
names: { sessionId: cookieName }
|
|
42
61
|
}
|
|
43
62
|
} = useRuntimeConfig(event).nuxtCore.authts;
|
|
63
|
+
const authRequestBypass = getAuthRequestBypass(event, { serverBypassRules, serverBypassRulesDev });
|
|
64
|
+
if (authRequestBypass === "hard") return;
|
|
44
65
|
const sessionCookieId = getCookie(event, cookieName);
|
|
45
66
|
const logger = useServerLogger({ event, tag: "plugin.auth" });
|
|
46
67
|
let session = await validateSession(sessionCookieId, {
|
|
@@ -93,11 +114,12 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
93
114
|
event.context.session = session;
|
|
94
115
|
});
|
|
95
116
|
nitroApp.hooks.hook("beforeResponse", async (event) => {
|
|
96
|
-
|
|
117
|
+
const { auth: { serverBypassRules, serverBypassRulesDev } = {}, sessions } = useRuntimeConfig(event).nuxtCore.authts;
|
|
118
|
+
if (getAuthRequestBypass(event, { serverBypassRules, serverBypassRulesDev }) === "hard") return;
|
|
97
119
|
if (event.node.res.headersSent) return;
|
|
98
120
|
const session = event.context.session;
|
|
99
121
|
if (session) {
|
|
100
|
-
writeSessionCookie(event, session,
|
|
122
|
+
writeSessionCookie(event, session, sessions);
|
|
101
123
|
}
|
|
102
124
|
});
|
|
103
125
|
});
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 📝 Nitro plugin for server-side logging integration.
|
|
3
|
+
*
|
|
4
|
+
* This plugin initializes the server logger and attaches it to the H3 event context (`event.context.logger`)
|
|
5
|
+
* for every incoming request. It also sets up a global error hook to automatically log uncaught
|
|
6
|
+
* Nitro server errors alongside their relevant request context (method and path).
|
|
7
|
+
*
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
*/
|
|
1
10
|
declare const _default: import("nitropack").NitroAppPlugin;
|
|
2
11
|
export default _default;
|
package/dist/types.d.mts
CHANGED
|
@@ -6,4 +6,4 @@ declare module '@nuxt/schema' {
|
|
|
6
6
|
|
|
7
7
|
export { default } from './module.mjs'
|
|
8
8
|
|
|
9
|
-
export { type AuthData, type AuthorizedUser, type AuthtsMiddlewareMeta, type AuthtsModuleOptions, type CoreLogInstance, type CoreLogLevel, type InputModuleOptions, type LoggingModuleOptions, type LoggingOptions, type ModuleOptions, type ModulePublicRuntimeConfig, type NuxtAuthOptions, type NuxtCoreLogging, type NuxtSessionContext, type NuxtSessionUpdateContext, type SessionContext } from './module.mjs'
|
|
9
|
+
export { type AuthData, type AuthRequestBypassMode, type AuthRequestBypassRule, type AuthorizedUser, type AuthtsMiddlewareMeta, type AuthtsModuleOptions, type CoreLogInstance, type CoreLogLevel, type InputModuleOptions, type LoggingModuleOptions, type LoggingOptions, type ModuleOptions, type ModulePublicRuntimeConfig, type NuxtAuthOptions, type NuxtCoreLogging, type NuxtSessionContext, type NuxtSessionUpdateContext, type SessionContext } from './module.mjs'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devcoffee/nuxt-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"cleanup": "nuxi cleanup && nuxi cleanup playground",
|
|
53
53
|
"dev:build": "nuxi build playground",
|
|
54
54
|
"prepack": "nuxt-module-build build",
|
|
55
|
-
"release": "npm run lint && npm run test:all && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
55
|
+
"release": "npm run lint && npm run test:types && npm run test:all && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
56
56
|
"lint": "eslint",
|
|
57
57
|
"lint:fix": "eslint --fix",
|
|
58
58
|
"test": "cross-env NODE_OPTIONS=--no-deprecation vitest run test/unit",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"test:e2e:ui": "cross-env NODE_OPTIONS=--no-deprecation PLAYWRIGHT_HEADLESS=false vitest run test/e2e",
|
|
61
61
|
"test:all": "cross-env NODE_OPTIONS=--no-deprecation vitest run",
|
|
62
62
|
"test:watch": "cross-env NODE_OPTIONS=--no-deprecation vitest watch test/unit",
|
|
63
|
-
"test:types": "vue-tsc --noEmit",
|
|
63
|
+
"test:types": "vue-tsc --noEmit -p .nuxt/tsconfig.app.json",
|
|
64
64
|
"typecheck": "vue-tsc --noEmit -p .nuxt/tsconfig.app.json"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|