@devcoffee/nuxt-core 1.4.2 → 1.5.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/CHANGELOG.md +240 -203
- package/README.md +2 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/server/core/crypto.js +3 -0
- package/dist/runtime/server/core/helpers.d.ts +6 -0
- package/dist/runtime/server/core/helpers.js +87 -25
- package/dist/runtime/server/core/mutex.d.ts +16 -3
- package/dist/runtime/server/core/mutex.js +27 -13
- package/dist/runtime/server/core/nuxtAuthtsHandler.js +1 -0
- package/dist/runtime/server/plugins/authts.js +7 -1
- package/package.json +3 -4
- package/GUIDELINE.md +0 -351
package/CHANGELOG.md
CHANGED
|
@@ -1,232 +1,269 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## v1.
|
|
3
|
+
## v1.5.0
|
|
4
4
|
|
|
5
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.4.
|
|
6
|
-
|
|
7
|
-
### 🩹 Fixes
|
|
8
|
-
|
|
9
|
-
- Handle user info fetch failures gracefully in auth handlers ([9111fd1](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/9111fd1))
|
|
10
|
-
|
|
11
|
-
### ❤️ Contributors
|
|
12
|
-
|
|
13
|
-
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
14
|
-
|
|
15
|
-
## v1.4.1
|
|
16
|
-
|
|
17
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.4.0...v1.4.1)
|
|
18
|
-
|
|
19
|
-
### 🩹 Fixes
|
|
20
|
-
|
|
21
|
-
- Normalize expiresIn to seconds across all session consumers ([2c197ab](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2c197ab))
|
|
22
|
-
|
|
23
|
-
### ❤️ Contributors
|
|
24
|
-
|
|
25
|
-
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
26
|
-
|
|
27
|
-
## v1.4.0
|
|
28
|
-
|
|
29
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.3.0...v1.4.0)
|
|
5
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.4.2...v1.5.0)
|
|
30
6
|
|
|
31
7
|
### 🚀 Enhancements
|
|
32
8
|
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Drop return from proxyRequest in NuxtForwardRequestHandler to prevent ERR_HTTP_HEADERS_SENT ([fd3b91c](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/fd3b91c))
|
|
38
|
-
- Prevent sending headers after response has been finalized in beforeResponse hook ([444a012](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/444a012))
|
|
39
|
-
- Update version to 1.3.2 in package.json and package-lock.json ([e6ab449](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/e6ab449))
|
|
40
|
-
|
|
41
|
-
### 💅 Refactors
|
|
42
|
-
|
|
43
|
-
- Remove redundant signSessionId checks from authts.sec03.test.ts ([18686f4](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/18686f4))
|
|
44
|
-
|
|
45
|
-
### ❤️ Contributors
|
|
46
|
-
|
|
47
|
-
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
48
|
-
|
|
49
|
-
## v1.3.0
|
|
50
|
-
|
|
51
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.5...v1.3.0)
|
|
52
|
-
|
|
53
|
-
### 🚀 Enhancements
|
|
54
|
-
|
|
55
|
-
- Enhance locale and timezone handling with session support in formatters plugin ([c36f621](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/c36f621))
|
|
56
|
-
|
|
57
|
-
### 🩹 Fixes
|
|
58
|
-
|
|
59
|
-
- Harden useAuthContext composable and logging infrastructure ([2a89ebf](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2a89ebf))
|
|
60
|
-
- Correct sessionCookieId reference in validateSession call in authts.ts tests ([2c8b343](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2c8b343))
|
|
61
|
-
|
|
62
|
-
### ❤️ Contributors
|
|
63
|
-
|
|
64
|
-
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
65
|
-
|
|
66
|
-
## v1.2.5
|
|
67
|
-
|
|
68
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.4...v1.2.5)
|
|
69
|
-
|
|
70
|
-
### 🩹 Fixes
|
|
71
|
-
|
|
72
|
-
- Update sourcemap configuration to reflect NODE_ENV for server and client ([4ded743](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/4ded743))
|
|
73
|
-
|
|
74
|
-
### ❤️ Contributors
|
|
75
|
-
|
|
76
|
-
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
77
|
-
|
|
78
|
-
## v1.2.4
|
|
79
|
-
|
|
80
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.3...v1.2.4)
|
|
81
|
-
|
|
82
|
-
### 🏡 Chore
|
|
83
|
-
|
|
84
|
-
- Exclude dist/runtime/server/tsconfig.json from published package ([6d6e02f](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/6d6e02f))
|
|
85
|
-
|
|
86
|
-
### ❤️ Contributors
|
|
87
|
-
|
|
88
|
-
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
89
|
-
|
|
90
|
-
## v1.2.3
|
|
91
|
-
|
|
92
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.2...v1.2.3)
|
|
93
|
-
|
|
94
|
-
## v1.2.2
|
|
95
|
-
|
|
96
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.1...v1.2.2)
|
|
9
|
+
- Add .gitnexusrc configuration file for project settings ([b3f8d23](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/b3f8d23))
|
|
10
|
+
- Restructure documentation by adding AGENTS.md and moving GUIDELINE.md to docs folder ([ff6ca21](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/ff6ca21))
|
|
11
|
+
- Enhance session management by validating HMAC format, clearing tokenSet on auth reset, and updating related tests ([ae2b6fb](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/ae2b6fb))
|
|
12
|
+
- Enhance session management by adding lock release functionality and improving token refresh logic ([506f3c5](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/506f3c5))
|
|
97
13
|
|
|
98
14
|
### 🩹 Fixes
|
|
99
15
|
|
|
100
|
-
-
|
|
16
|
+
- Enhance refreshTokenIfNeeded to support sessionCreateOptions for improved session handling ([609a4a4](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/609a4a4))
|
|
17
|
+
- Update release script to include changelogen for better version management ([853cfbb](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/853cfbb))
|
|
101
18
|
|
|
102
19
|
### 🏡 Chore
|
|
103
20
|
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
### ❤️ Contributors
|
|
107
|
-
|
|
108
|
-
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
109
|
-
|
|
110
|
-
## v1.2.1
|
|
111
|
-
|
|
112
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.0...v1.2.1)
|
|
113
|
-
|
|
114
|
-
### 🩹 Fixes
|
|
115
|
-
|
|
116
|
-
- Resolve session double encryption issue by decrypting tokenSet in validateSession and updating getSession usage in updateSession ([6ed8fb6](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/6ed8fb6))
|
|
117
|
-
- Populate nodeReferences and sharedReferences in prepare:types hook ([83b41e2](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/83b41e2))
|
|
118
|
-
- Include devcoffee-nitro-core.d.ts in server TypeScript project via nitro:config hook ([20b35e0](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/20b35e0))
|
|
119
|
-
- **typecheck:** Exclude test, playground, and eslint config from tsconfig.app.json ([9d9c857](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/9d9c857))
|
|
21
|
+
- **release:** V1.4.3 ([0b430bd](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/0b430bd))
|
|
120
22
|
|
|
121
23
|
### ❤️ Contributors
|
|
122
24
|
|
|
123
25
|
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
124
26
|
|
|
125
|
-
## v1.
|
|
27
|
+
## v1.4.3
|
|
126
28
|
|
|
127
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.
|
|
128
|
-
|
|
129
|
-
### 🚀 Enhancements
|
|
130
|
-
|
|
131
|
-
- **quick-260328-uf7:** Create admin-board test fixture ([45dae18](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/45dae18))
|
|
132
|
-
- **quick-260328-uf7:** Add admin-board OIDC e2e test ([20bf504](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/20bf504))
|
|
29
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.4.2...v1.4.3)
|
|
133
30
|
|
|
134
31
|
### 🩹 Fixes
|
|
135
32
|
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
- Resolve #devcoffee-core subpath import warnings in build ([#19](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/19))
|
|
139
|
-
- Eliminate duplicate Redis sessions on SSR first load and refactor event.context.session ([#20](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/20))
|
|
140
|
-
- Rotate session on logout and remove console.log debug statement ([fbe129f](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/fbe129f))
|
|
141
|
-
- Resolve autoFetchUser SSR divergence on first render ([1f15ac7](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/1f15ac7))
|
|
142
|
-
- **lint:** Remove unused destructure aliases in SSR session mapping ([cfe7884](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/cfe7884))
|
|
143
|
-
|
|
144
|
-
### 📖 Documentation
|
|
145
|
-
|
|
146
|
-
- **quick:** Create plan for admin-board OIDC e2e test ([ce8c611](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/ce8c611))
|
|
147
|
-
- **quick-260328-uf7:** Complete admin-board OIDC e2e plan ([23c0945](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/23c0945))
|
|
148
|
-
|
|
149
|
-
### ✅ Tests
|
|
150
|
-
|
|
151
|
-
- Update deleteSession tests to reflect renewSession LOGOUT behavior ([b444baa](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/b444baa))
|
|
33
|
+
- Enhance refreshTokenIfNeeded to support sessionCreateOptions for improved session handling ([609a4a4](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/609a4a4))
|
|
34
|
+
- Update release script to include changelogen for better version management ([853cfbb](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/853cfbb))
|
|
152
35
|
|
|
153
36
|
### ❤️ Contributors
|
|
154
37
|
|
|
155
38
|
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
156
|
-
- Hiếu Nguyễn ([@coolkg1412](https://github.com/coolkg1412))
|
|
157
|
-
|
|
158
|
-
## v1.1.1
|
|
159
|
-
|
|
160
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.0...v1.1.1)
|
|
161
|
-
|
|
162
|
-
### 💅 Refactors
|
|
163
|
-
|
|
164
|
-
- Sort storage adapter imports in helpers.ts; remove unused moduleText in forward handler test ([20f3496](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/20f3496))
|
|
165
|
-
|
|
166
|
-
### 🏡 Chore
|
|
167
|
-
|
|
168
|
-
- Complete v1.0 milestone — archive roadmap, requirements, retrospective ([2cb5067](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2cb5067))
|
|
169
|
-
- Delete REQUIREMENTS.md — archived to milestones/v1.0-REQUIREMENTS.md ([48ce751](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/48ce751))
|
|
170
|
-
- Archive phase directories from v1.0 milestone ([1f3ced8](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/1f3ced8))
|
|
171
|
-
|
|
172
|
-
### ❤️ Contributors
|
|
173
|
-
|
|
174
|
-
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
175
|
-
|
|
176
|
-
## v1.1.0
|
|
177
|
-
|
|
178
|
-
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.0.2...v1.1.0)
|
|
179
|
-
|
|
180
|
-
### 🚀 Enhancements
|
|
181
|
-
|
|
182
|
-
- Add support for ignored authentication path regex patterns ([#5](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/5))
|
|
183
|
-
- **04:** Adapter foundation — http/utils/storage/oidc barrel adapters (ADPT-01–04) ([3c44a78](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/3c44a78))
|
|
184
|
-
- **09:** Add E2E test coverage using Playwright ([#14](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/14))
|
|
185
|
-
- **docs:** Phase 10 — README.md and GUIDELINE.md for consumers and contributors ([#15](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/15))
|
|
186
|
-
- **11:** Distributed token refresh mutex — atomic Redis NX lock for multi-instance deployments ([f7e2d72](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/f7e2d72))
|
|
187
|
-
|
|
188
|
-
### 🩹 Fixes
|
|
189
|
-
|
|
190
|
-
- TypeScript ([#4](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/4))
|
|
191
|
-
- Update index page test to check for non-null response ([aba39bf](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/aba39bf))
|
|
192
|
-
- **lint:** Auto-fix prettier formatting and replace dynamic delete in omit() ([89d987a](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/89d987a))
|
|
193
|
-
- **test:** Update SEC-03 source-text assertion to match prettier-formatted multiline call ([55f92d7](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/55f92d7))
|
|
194
|
-
- **types:** Resolve TypeScript strict-mode errors across app layer and server core ([b089812](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/b089812))
|
|
195
|
-
- **types:** Cast cookieOpts input before omit to resolve TS key narrowing error ([93dbb50](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/93dbb50))
|
|
196
|
-
- **types:** Include global.d.ts in Nitro server tsconfig to resolve DeepPartial on server layer ([24700e2](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/24700e2))
|
|
197
|
-
|
|
198
|
-
### 💅 Refactors
|
|
199
|
-
|
|
200
|
-
- Flatten utils paths (utils/utils.ts → utils.ts) ([45d8997](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/45d8997))
|
|
201
|
-
- **test:** Migrate test imports to @/ alias and fix tsconfig/vitest paths ([1f73e73](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/1f73e73))
|
|
202
|
-
- **utils:** Consolidate utility exports and fix module import paths ([d9e2fdc](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/d9e2fdc))
|
|
203
|
-
- **module:** Remove redundant typescript tsConfig includes covered by prepare:types hook ([9ced2c5](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/9ced2c5))
|
|
204
|
-
|
|
205
|
-
### 📖 Documentation
|
|
206
|
-
|
|
207
|
-
- **10:** Research phase for README.md and GUIDELINE.md documentation ([31d5ab0](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/31d5ab0))
|
|
208
|
-
- Resolve debug eslint-ts-type-errors ([0653761](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/0653761))
|
|
209
|
-
- Update debug knowledge base with eslint-ts-type-errors ([2dc0cc1](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2dc0cc1))
|
|
210
|
-
|
|
211
|
-
### 🏡 Chore
|
|
212
|
-
|
|
213
|
-
- Authorize for devecoffee ([#2](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/2))
|
|
214
|
-
- The base UI and common Components ([#3](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/3))
|
|
215
|
-
- Update docs ([b708e93](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/b708e93))
|
|
216
|
-
- Suppress DEP0155 deprecation warning in test scripts ([6fdb696](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/6fdb696))
|
|
217
|
-
- **test:** Split test scripts and enable headed E2E mode ([207c3d5](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/207c3d5))
|
|
218
|
-
- Bump `compatibilityDate` ([2654eb2](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2654eb2))
|
|
219
|
-
|
|
220
|
-
### ✅ Tests
|
|
221
|
-
|
|
222
|
-
- **api:** Mark BUG-03 deprecation tests as todo until implemented ([69cd623](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/69cd623))
|
|
223
|
-
- Update import aliases and trim stale todo tests for BUG-03 ([f7c5cff](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/f7c5cff))
|
|
224
|
-
|
|
225
|
-
### ❤️ Contributors
|
|
226
|
-
|
|
227
|
-
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
228
|
-
- Hiếu Nguyễn ([@coolkg1412](https://github.com/coolkg1412))
|
|
229
39
|
|
|
40
|
+
## v1.4.2
|
|
41
|
+
|
|
42
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.4.1...v1.4.2)
|
|
43
|
+
|
|
44
|
+
### 🩹 Fixes
|
|
45
|
+
|
|
46
|
+
- Handle user info fetch failures gracefully in auth handlers ([9111fd1](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/9111fd1))
|
|
47
|
+
|
|
48
|
+
### ❤️ Contributors
|
|
49
|
+
|
|
50
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
51
|
+
|
|
52
|
+
## v1.4.1
|
|
53
|
+
|
|
54
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.4.0...v1.4.1)
|
|
55
|
+
|
|
56
|
+
### 🩹 Fixes
|
|
57
|
+
|
|
58
|
+
- Normalize expiresIn to seconds across all session consumers ([2c197ab](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2c197ab))
|
|
59
|
+
|
|
60
|
+
### ❤️ Contributors
|
|
61
|
+
|
|
62
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
63
|
+
|
|
64
|
+
## v1.4.0
|
|
65
|
+
|
|
66
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.3.0...v1.4.0)
|
|
67
|
+
|
|
68
|
+
### 🚀 Enhancements
|
|
69
|
+
|
|
70
|
+
- Implement writeSessionCookie function and integrate it into NuxtForwardRequestHandler and authts plugin ([e761f6c](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/e761f6c))
|
|
71
|
+
|
|
72
|
+
### 🩹 Fixes
|
|
73
|
+
|
|
74
|
+
- Drop return from proxyRequest in NuxtForwardRequestHandler to prevent ERR_HTTP_HEADERS_SENT ([fd3b91c](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/fd3b91c))
|
|
75
|
+
- Prevent sending headers after response has been finalized in beforeResponse hook ([444a012](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/444a012))
|
|
76
|
+
- Update version to 1.3.2 in package.json and package-lock.json ([e6ab449](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/e6ab449))
|
|
77
|
+
|
|
78
|
+
### 💅 Refactors
|
|
79
|
+
|
|
80
|
+
- Remove redundant signSessionId checks from authts.sec03.test.ts ([18686f4](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/18686f4))
|
|
81
|
+
|
|
82
|
+
### ❤️ Contributors
|
|
83
|
+
|
|
84
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
85
|
+
|
|
86
|
+
## v1.3.0
|
|
87
|
+
|
|
88
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.5...v1.3.0)
|
|
89
|
+
|
|
90
|
+
### 🚀 Enhancements
|
|
91
|
+
|
|
92
|
+
- Enhance locale and timezone handling with session support in formatters plugin ([c36f621](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/c36f621))
|
|
93
|
+
|
|
94
|
+
### 🩹 Fixes
|
|
95
|
+
|
|
96
|
+
- Harden useAuthContext composable and logging infrastructure ([2a89ebf](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2a89ebf))
|
|
97
|
+
- Correct sessionCookieId reference in validateSession call in authts.ts tests ([2c8b343](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2c8b343))
|
|
98
|
+
|
|
99
|
+
### ❤️ Contributors
|
|
100
|
+
|
|
101
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
102
|
+
|
|
103
|
+
## v1.2.5
|
|
104
|
+
|
|
105
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.4...v1.2.5)
|
|
106
|
+
|
|
107
|
+
### 🩹 Fixes
|
|
108
|
+
|
|
109
|
+
- Update sourcemap configuration to reflect NODE_ENV for server and client ([4ded743](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/4ded743))
|
|
110
|
+
|
|
111
|
+
### ❤️ Contributors
|
|
112
|
+
|
|
113
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
114
|
+
|
|
115
|
+
## v1.2.4
|
|
116
|
+
|
|
117
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.3...v1.2.4)
|
|
118
|
+
|
|
119
|
+
### 🏡 Chore
|
|
120
|
+
|
|
121
|
+
- Exclude dist/runtime/server/tsconfig.json from published package ([6d6e02f](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/6d6e02f))
|
|
122
|
+
|
|
123
|
+
### ❤️ Contributors
|
|
124
|
+
|
|
125
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
126
|
+
|
|
127
|
+
## v1.2.3
|
|
128
|
+
|
|
129
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.2...v1.2.3)
|
|
130
|
+
|
|
131
|
+
## v1.2.2
|
|
132
|
+
|
|
133
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.1...v1.2.2)
|
|
134
|
+
|
|
135
|
+
### 🩹 Fixes
|
|
136
|
+
|
|
137
|
+
- Ensure sanitizeError function returns H3Error type consistently ([de54a04](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/de54a04))
|
|
138
|
+
|
|
139
|
+
### 🏡 Chore
|
|
140
|
+
|
|
141
|
+
- Add publishConfig access public for scoped npm package ([3c080be](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/3c080be))
|
|
142
|
+
|
|
143
|
+
### ❤️ Contributors
|
|
144
|
+
|
|
145
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
146
|
+
|
|
147
|
+
## v1.2.1
|
|
148
|
+
|
|
149
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.2.0...v1.2.1)
|
|
150
|
+
|
|
151
|
+
### 🩹 Fixes
|
|
152
|
+
|
|
153
|
+
- Resolve session double encryption issue by decrypting tokenSet in validateSession and updating getSession usage in updateSession ([6ed8fb6](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/6ed8fb6))
|
|
154
|
+
- Populate nodeReferences and sharedReferences in prepare:types hook ([83b41e2](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/83b41e2))
|
|
155
|
+
- Include devcoffee-nitro-core.d.ts in server TypeScript project via nitro:config hook ([20b35e0](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/20b35e0))
|
|
156
|
+
- **typecheck:** Exclude test, playground, and eslint config from tsconfig.app.json ([9d9c857](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/9d9c857))
|
|
157
|
+
|
|
158
|
+
### ❤️ Contributors
|
|
159
|
+
|
|
160
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
161
|
+
|
|
162
|
+
## v1.2.0
|
|
163
|
+
|
|
164
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.1.1...v1.2.0)
|
|
165
|
+
|
|
166
|
+
### 🚀 Enhancements
|
|
167
|
+
|
|
168
|
+
- **quick-260328-uf7:** Create admin-board test fixture ([45dae18](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/45dae18))
|
|
169
|
+
- **quick-260328-uf7:** Add admin-board OIDC e2e test ([20bf504](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/20bf504))
|
|
170
|
+
|
|
171
|
+
### 🩹 Fixes
|
|
172
|
+
|
|
173
|
+
- **types:** Explicitly type module export as NuxtModule for better type inference ([33e140e](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/33e140e))
|
|
174
|
+
- **types:** Resolve TypeScript errors in middleware and plugins ([df23475](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/df23475))
|
|
175
|
+
- Resolve #devcoffee-core subpath import warnings in build ([#19](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/19))
|
|
176
|
+
- Eliminate duplicate Redis sessions on SSR first load and refactor event.context.session ([#20](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/20))
|
|
177
|
+
- Rotate session on logout and remove console.log debug statement ([fbe129f](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/fbe129f))
|
|
178
|
+
- Resolve autoFetchUser SSR divergence on first render ([1f15ac7](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/1f15ac7))
|
|
179
|
+
- **lint:** Remove unused destructure aliases in SSR session mapping ([cfe7884](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/cfe7884))
|
|
180
|
+
|
|
181
|
+
### 📖 Documentation
|
|
182
|
+
|
|
183
|
+
- **quick:** Create plan for admin-board OIDC e2e test ([ce8c611](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/ce8c611))
|
|
184
|
+
- **quick-260328-uf7:** Complete admin-board OIDC e2e plan ([23c0945](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/23c0945))
|
|
185
|
+
|
|
186
|
+
### ✅ Tests
|
|
187
|
+
|
|
188
|
+
- Update deleteSession tests to reflect renewSession LOGOUT behavior ([b444baa](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/b444baa))
|
|
189
|
+
|
|
190
|
+
### ❤️ Contributors
|
|
191
|
+
|
|
192
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
193
|
+
- Hiếu Nguyễn ([@coolkg1412](https://github.com/coolkg1412))
|
|
194
|
+
|
|
195
|
+
## v1.1.1
|
|
196
|
+
|
|
197
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.0...v1.1.1)
|
|
198
|
+
|
|
199
|
+
### 💅 Refactors
|
|
200
|
+
|
|
201
|
+
- Sort storage adapter imports in helpers.ts; remove unused moduleText in forward handler test ([20f3496](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/20f3496))
|
|
202
|
+
|
|
203
|
+
### 🏡 Chore
|
|
204
|
+
|
|
205
|
+
- Complete v1.0 milestone — archive roadmap, requirements, retrospective ([2cb5067](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2cb5067))
|
|
206
|
+
- Delete REQUIREMENTS.md — archived to milestones/v1.0-REQUIREMENTS.md ([48ce751](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/48ce751))
|
|
207
|
+
- Archive phase directories from v1.0 milestone ([1f3ced8](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/1f3ced8))
|
|
208
|
+
|
|
209
|
+
### ❤️ Contributors
|
|
210
|
+
|
|
211
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
212
|
+
|
|
213
|
+
## v1.1.0
|
|
214
|
+
|
|
215
|
+
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.0.2...v1.1.0)
|
|
216
|
+
|
|
217
|
+
### 🚀 Enhancements
|
|
218
|
+
|
|
219
|
+
- Add support for ignored authentication path regex patterns ([#5](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/5))
|
|
220
|
+
- **04:** Adapter foundation — http/utils/storage/oidc barrel adapters (ADPT-01–04) ([3c44a78](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/3c44a78))
|
|
221
|
+
- **09:** Add E2E test coverage using Playwright ([#14](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/14))
|
|
222
|
+
- **docs:** Phase 10 — README.md and GUIDELINE.md for consumers and contributors ([#15](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/15))
|
|
223
|
+
- **11:** Distributed token refresh mutex — atomic Redis NX lock for multi-instance deployments ([f7e2d72](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/f7e2d72))
|
|
224
|
+
|
|
225
|
+
### 🩹 Fixes
|
|
226
|
+
|
|
227
|
+
- TypeScript ([#4](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/4))
|
|
228
|
+
- Update index page test to check for non-null response ([aba39bf](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/aba39bf))
|
|
229
|
+
- **lint:** Auto-fix prettier formatting and replace dynamic delete in omit() ([89d987a](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/89d987a))
|
|
230
|
+
- **test:** Update SEC-03 source-text assertion to match prettier-formatted multiline call ([55f92d7](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/55f92d7))
|
|
231
|
+
- **types:** Resolve TypeScript strict-mode errors across app layer and server core ([b089812](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/b089812))
|
|
232
|
+
- **types:** Cast cookieOpts input before omit to resolve TS key narrowing error ([93dbb50](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/93dbb50))
|
|
233
|
+
- **types:** Include global.d.ts in Nitro server tsconfig to resolve DeepPartial on server layer ([24700e2](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/24700e2))
|
|
234
|
+
|
|
235
|
+
### 💅 Refactors
|
|
236
|
+
|
|
237
|
+
- Flatten utils paths (utils/utils.ts → utils.ts) ([45d8997](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/45d8997))
|
|
238
|
+
- **test:** Migrate test imports to @/ alias and fix tsconfig/vitest paths ([1f73e73](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/1f73e73))
|
|
239
|
+
- **utils:** Consolidate utility exports and fix module import paths ([d9e2fdc](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/d9e2fdc))
|
|
240
|
+
- **module:** Remove redundant typescript tsConfig includes covered by prepare:types hook ([9ced2c5](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/9ced2c5))
|
|
241
|
+
|
|
242
|
+
### 📖 Documentation
|
|
243
|
+
|
|
244
|
+
- **10:** Research phase for README.md and GUIDELINE.md documentation ([31d5ab0](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/31d5ab0))
|
|
245
|
+
- Resolve debug eslint-ts-type-errors ([0653761](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/0653761))
|
|
246
|
+
- Update debug knowledge base with eslint-ts-type-errors ([2dc0cc1](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2dc0cc1))
|
|
247
|
+
|
|
248
|
+
### 🏡 Chore
|
|
249
|
+
|
|
250
|
+
- Authorize for devecoffee ([#2](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/2))
|
|
251
|
+
- The base UI and common Components ([#3](https://github.com/coolkg1412/devcoffee-nuxt-core/pull/3))
|
|
252
|
+
- Update docs ([b708e93](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/b708e93))
|
|
253
|
+
- Suppress DEP0155 deprecation warning in test scripts ([6fdb696](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/6fdb696))
|
|
254
|
+
- **test:** Split test scripts and enable headed E2E mode ([207c3d5](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/207c3d5))
|
|
255
|
+
- Bump `compatibilityDate` ([2654eb2](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/2654eb2))
|
|
256
|
+
|
|
257
|
+
### ✅ Tests
|
|
258
|
+
|
|
259
|
+
- **api:** Mark BUG-03 deprecation tests as todo until implemented ([69cd623](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/69cd623))
|
|
260
|
+
- Update import aliases and trim stale todo tests for BUG-03 ([f7c5cff](https://github.com/coolkg1412/devcoffee-nuxt-core/commit/f7c5cff))
|
|
261
|
+
|
|
262
|
+
### ❤️ Contributors
|
|
263
|
+
|
|
264
|
+
- Hieu Nguyen <hieu.nguyen@devcoffee.tech>
|
|
265
|
+
- Hiếu Nguyễn ([@coolkg1412](https://github.com/coolkg1412))
|
|
266
|
+
|
|
230
267
|
## v1.0.2
|
|
231
268
|
|
|
232
269
|
[compare changes](https://github.com/coolkg1412/devcoffee-nuxt-core/compare/v1.0.1...v1.0.2)
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Full OpenID Connect / OAuth 2.0 authorization code grant with PKCE for DevCoffee
|
|
|
9
9
|
Provides server-side session management via Nitro, client-side auth state via Vue composables, and universal route protection middleware.
|
|
10
10
|
|
|
11
11
|
- [Release Notes](/CHANGELOG.md)
|
|
12
|
-
- [Contributor Guide](/GUIDELINE.md)
|
|
12
|
+
- [Contributor Guide](/docs/GUIDELINE.md)
|
|
13
13
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
@@ -389,7 +389,7 @@ See [CHANGELOG.md](/CHANGELOG.md) for the full release history.
|
|
|
389
389
|
|
|
390
390
|
## Contributing
|
|
391
391
|
|
|
392
|
-
See [GUIDELINE.md](/GUIDELINE.md) for contribution guidelines, local development setup, and release instructions.
|
|
392
|
+
See [GUIDELINE.md](/docs/GUIDELINE.md) for contribution guidelines, local development setup, and release instructions.
|
|
393
393
|
|
|
394
394
|
<!-- Badges -->
|
|
395
395
|
[npm-version-src]: https://img.shields.io/npm/v/@devcoffee/nuxt-core/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -2,7 +2,7 @@ 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.5.0";
|
|
6
6
|
|
|
7
7
|
const defaultLocale = "vi-VN";
|
|
8
8
|
const defaultLanguage = "vi";
|
|
@@ -21,6 +21,9 @@ export function verifySessionId(cookieValue, secret) {
|
|
|
21
21
|
}
|
|
22
22
|
const sessionId = cookieValue.slice(0, 64);
|
|
23
23
|
const providedSig = cookieValue.slice(65);
|
|
24
|
+
if (!/^[0-9a-f]{64}$/i.test(providedSig)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
24
27
|
const expected = createHmac("sha256", secret).update(sessionId).digest("hex");
|
|
25
28
|
const valid = timingSafeEqual(Buffer.from(providedSig, "hex"), Buffer.from(expected, "hex"));
|
|
26
29
|
return valid ? sessionId : null;
|
|
@@ -198,6 +198,12 @@ export declare function refreshTokenIfNeeded(session: SessionContext, opts: {
|
|
|
198
198
|
clientSecret: string;
|
|
199
199
|
tokenRefreshBufferMs: number;
|
|
200
200
|
distributedLock?: boolean;
|
|
201
|
+
sessionCreateOptions?: {
|
|
202
|
+
storageName: string;
|
|
203
|
+
storagePrefix: string;
|
|
204
|
+
expiresIn?: number;
|
|
205
|
+
secret: string;
|
|
206
|
+
};
|
|
201
207
|
}): Promise<any>;
|
|
202
208
|
/**
|
|
203
209
|
* Fetch user profile information from the OpenID Provider using the access token.
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
signSessionId,
|
|
31
31
|
verifySessionId
|
|
32
32
|
} from "./crypto.js";
|
|
33
|
-
import { tryAcquireLock } from "./mutex.js";
|
|
33
|
+
import { releaseLock, tryAcquireLock } from "./mutex.js";
|
|
34
34
|
function getAnonymousUser(extras) {
|
|
35
35
|
const anonymous = useRuntimeConfig().nuxtCore.authts.auth.anonymousUser;
|
|
36
36
|
const { defaultLocale, defaultTimeZone, defaultLanguage } = useRuntimeConfig().nuxtCore;
|
|
@@ -160,6 +160,9 @@ export async function updateSession(sessionId, input, opts) {
|
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
162
|
session = deepMerge({}, session, normalizedInput);
|
|
163
|
+
if (normalizedInput.auth && Object.prototype.hasOwnProperty.call(normalizedInput.auth, "tokenSet") && normalizedInput.auth.tokenSet === void 0) {
|
|
164
|
+
delete session.auth.tokenSet;
|
|
165
|
+
}
|
|
163
166
|
session.expiresAt = now + opts.expiresIn * 1e3;
|
|
164
167
|
const sessionToStore = { ...session };
|
|
165
168
|
if (opts.secret && sessionToStore.auth?.tokenSet) {
|
|
@@ -216,7 +219,7 @@ export async function discoveryOpendId(wellKnownUrl, opts) {
|
|
|
216
219
|
server: config.serverMetadata(),
|
|
217
220
|
client: config.clientMetadata()
|
|
218
221
|
};
|
|
219
|
-
await storage.setItem(cacheKey, meta, { ttl: expires
|
|
222
|
+
await storage.setItem(cacheKey, meta, { ttl: expires });
|
|
220
223
|
logger.info('Fetching OpenID Connect metadata from "%s"', wellKnownUrl);
|
|
221
224
|
}
|
|
222
225
|
return meta;
|
|
@@ -267,7 +270,7 @@ export async function buildAuthorizationUrl(session, opt) {
|
|
|
267
270
|
}
|
|
268
271
|
await updateSession(
|
|
269
272
|
session.id,
|
|
270
|
-
{ auth: { status: "unauthenticated" } },
|
|
273
|
+
{ auth: { status: "unauthenticated", tokenSet: void 0 } },
|
|
271
274
|
{
|
|
272
275
|
storageName: sessionStorageName,
|
|
273
276
|
storagePrefix: sessionStoragePrefix,
|
|
@@ -324,7 +327,7 @@ export function constructTokenSet(input) {
|
|
|
324
327
|
}
|
|
325
328
|
export async function refreshTokenIfNeeded(session, opts) {
|
|
326
329
|
const logger = useServerLogger({ tag: "authts-helper" });
|
|
327
|
-
let
|
|
330
|
+
let sessionUpdate = {
|
|
328
331
|
auth: { status: "unauthenticated", tokenSet: void 0 },
|
|
329
332
|
user: getAnonymousUser()
|
|
330
333
|
};
|
|
@@ -332,7 +335,7 @@ export async function refreshTokenIfNeeded(session, opts) {
|
|
|
332
335
|
const { accessToken, refreshToken, expiresAt } = session.auth.tokenSet;
|
|
333
336
|
const accessExpired = Boolean(accessToken && expiresAt - opts.tokenRefreshBufferMs < Date.now());
|
|
334
337
|
if (!accessExpired) {
|
|
335
|
-
|
|
338
|
+
sessionUpdate = {
|
|
336
339
|
auth: {
|
|
337
340
|
status: session.auth.status,
|
|
338
341
|
tokenSet: session.auth.tokenSet
|
|
@@ -342,29 +345,88 @@ export async function refreshTokenIfNeeded(session, opts) {
|
|
|
342
345
|
} else if (accessExpired && refreshToken) {
|
|
343
346
|
const lockStorage = useStorage("cache");
|
|
344
347
|
const lockKey = `${opts.cache.prefix}:refresh-lock:${session.id}`;
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
348
|
+
const waitDeadline = Date.now() + 5e3;
|
|
349
|
+
let lease = await tryAcquireLock(lockStorage, lockKey, 10, opts.distributedLock ?? false);
|
|
350
|
+
const sessionCreateOptions = opts.sessionCreateOptions;
|
|
351
|
+
if (!lease) {
|
|
352
|
+
if (!sessionCreateOptions) {
|
|
353
|
+
logger.warn("[refreshTokenIfNeeded] sessionCreateOptions not provided, cannot wait for concurrent refresh");
|
|
354
|
+
return {};
|
|
355
|
+
}
|
|
356
|
+
while (!lease && Date.now() < waitDeadline) {
|
|
357
|
+
logger.debug('[refreshTokenIfNeeded] refresh lock held for session "%s" \u2014 waiting...', session.id);
|
|
358
|
+
const { storageName, storagePrefix, secret } = sessionCreateOptions;
|
|
359
|
+
const updatedSession = await getSession(session.id, { storageName, storagePrefix, secret });
|
|
360
|
+
if (updatedSession?.auth?.tokenSet) {
|
|
361
|
+
const newExpiresAt = updatedSession.auth.tokenSet.expiresAt;
|
|
362
|
+
const isStillExpired = Boolean(newExpiresAt - opts.tokenRefreshBufferMs < Date.now());
|
|
363
|
+
if (!isStillExpired) {
|
|
364
|
+
logger.debug("[refreshTokenIfNeeded] session refreshed by another request, proceeding.");
|
|
365
|
+
return {
|
|
366
|
+
auth: { status: updatedSession.auth.status, tokenSet: updatedSession.auth.tokenSet },
|
|
367
|
+
user: updatedSession.user
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
372
|
+
if (!await lockStorage.hasItem(lockKey)) {
|
|
373
|
+
const updatedSessionAfterRelease = await getSession(session.id, { storageName, storagePrefix, secret });
|
|
374
|
+
if (updatedSessionAfterRelease?.auth?.tokenSet) {
|
|
375
|
+
const newExpiresAt = updatedSessionAfterRelease.auth.tokenSet.expiresAt;
|
|
376
|
+
const isStillExpired = Boolean(newExpiresAt - opts.tokenRefreshBufferMs < Date.now());
|
|
377
|
+
if (!isStillExpired) {
|
|
378
|
+
logger.debug("[refreshTokenIfNeeded] session refreshed by another request after lock release.");
|
|
379
|
+
return {
|
|
380
|
+
auth: {
|
|
381
|
+
status: updatedSessionAfterRelease.auth.status,
|
|
382
|
+
tokenSet: updatedSessionAfterRelease.auth.tokenSet
|
|
383
|
+
},
|
|
384
|
+
user: updatedSessionAfterRelease.user
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
lease = await tryAcquireLock(lockStorage, lockKey, 10, opts.distributedLock ?? false);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (!lease) {
|
|
393
|
+
logger.warn('[refreshTokenIfNeeded] Timeout waiting for refresh lock for session "%s"', session.id);
|
|
394
|
+
return {};
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
logger.info('Refreshing access token for session "%s"', session.id);
|
|
398
|
+
const tokenSet = await refreshTokenGrant(refreshToken, {
|
|
399
|
+
wellKnownUrl: opts.wellKnownUrl,
|
|
400
|
+
cache: opts.cache,
|
|
401
|
+
clientId: opts.clientId,
|
|
402
|
+
clientSecret: opts.clientSecret
|
|
403
|
+
});
|
|
404
|
+
await lockStorage.removeItem(`${opts.cache.prefix}:userinfo:${session.id}`);
|
|
405
|
+
sessionUpdate = {
|
|
406
|
+
auth: {
|
|
407
|
+
status: session.auth.status,
|
|
408
|
+
tokenSet: constructTokenSet(tokenSet)
|
|
409
|
+
},
|
|
410
|
+
user: session.user
|
|
411
|
+
};
|
|
412
|
+
if (opts.sessionCreateOptions?.expiresIn) {
|
|
413
|
+
const { storageName, storagePrefix, expiresIn, secret } = opts.sessionCreateOptions;
|
|
414
|
+
await updateSession(session.id, sessionUpdate, { storageName, storagePrefix, expiresIn, secret });
|
|
415
|
+
}
|
|
416
|
+
} finally {
|
|
417
|
+
try {
|
|
418
|
+
await releaseLock(lockStorage, lease);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
logger.warn(
|
|
421
|
+
'[refreshTokenIfNeeded] failed to release refresh lock for session "%s": %s',
|
|
422
|
+
session.id,
|
|
423
|
+
String(err)
|
|
424
|
+
);
|
|
425
|
+
}
|
|
349
426
|
}
|
|
350
|
-
logger.info('Refreshing access token for session "%s"', session.id);
|
|
351
|
-
const tokenSet = await refreshTokenGrant(refreshToken, {
|
|
352
|
-
wellKnownUrl: opts.wellKnownUrl,
|
|
353
|
-
cache: opts.cache,
|
|
354
|
-
clientId: opts.clientId,
|
|
355
|
-
clientSecret: opts.clientSecret
|
|
356
|
-
});
|
|
357
|
-
await lockStorage.removeItem(`${opts.cache.prefix}:userinfo:${session.id}`);
|
|
358
|
-
updateSession2 = {
|
|
359
|
-
auth: {
|
|
360
|
-
status: session.auth.status,
|
|
361
|
-
tokenSet: constructTokenSet(tokenSet)
|
|
362
|
-
},
|
|
363
|
-
user: session.user
|
|
364
|
-
};
|
|
365
427
|
}
|
|
366
428
|
}
|
|
367
|
-
return
|
|
429
|
+
return sessionUpdate;
|
|
368
430
|
}
|
|
369
431
|
export async function fetchUserInfo(accessToken, sub, opts) {
|
|
370
432
|
const { wellKnownUrl, cache, clientId, clientSecret } = opts;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { Storage } from 'unstorage';
|
|
2
|
+
type RedisLockClient = {
|
|
3
|
+
set: (...args: unknown[]) => Promise<string | null>;
|
|
4
|
+
eval?: (...args: unknown[]) => Promise<unknown>;
|
|
5
|
+
};
|
|
6
|
+
export type LockLease = {
|
|
7
|
+
key: string;
|
|
8
|
+
owner: string;
|
|
9
|
+
atomic: boolean;
|
|
10
|
+
prefixedKey?: string;
|
|
11
|
+
client?: RedisLockClient;
|
|
12
|
+
};
|
|
2
13
|
/**
|
|
3
14
|
* Attempts to acquire a distributed or optimistic mutex lock in Nitro cache storage.
|
|
4
15
|
*
|
|
@@ -11,9 +22,11 @@ import type { Storage } from 'unstorage';
|
|
|
11
22
|
*
|
|
12
23
|
* @param storage - Prefixed cache storage (`useStorage('cache')`). Used for the optimistic path.
|
|
13
24
|
* @param lockKey - The lock key (without the cache prefix). Must be unique per session.
|
|
14
|
-
* @param ttlSeconds - Lock TTL in seconds. Lock expires automatically
|
|
25
|
+
* @param ttlSeconds - Lock TTL in seconds. Lock expires automatically if release is skipped.
|
|
15
26
|
* @param useAtomic - When `true`, attempt atomic NX acquisition via Redis native client.
|
|
16
|
-
* @returns
|
|
27
|
+
* @returns A lock lease if acquired by this caller, otherwise `null` if the lock was already held.
|
|
17
28
|
* @since 1.0.0
|
|
18
29
|
*/
|
|
19
|
-
export declare function tryAcquireLock(storage: Storage, lockKey: string, ttlSeconds: number, useAtomic: boolean): Promise<
|
|
30
|
+
export declare function tryAcquireLock(storage: Storage, lockKey: string, ttlSeconds: number, useAtomic: boolean, owner?: string): Promise<LockLease | null>;
|
|
31
|
+
export declare function releaseLock(storage: Storage, lease: LockLease): Promise<void>;
|
|
32
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import useServerLogger from "#devcoffee-core/server/composables/useServerLogger";
|
|
2
3
|
import { useStorage } from "nitropack/runtime";
|
|
3
|
-
export async function tryAcquireLock(storage, lockKey, ttlSeconds, useAtomic) {
|
|
4
|
+
export async function tryAcquireLock(storage, lockKey, ttlSeconds, useAtomic, owner = randomUUID()) {
|
|
4
5
|
const logger = useServerLogger({ tag: "authts-mutex" });
|
|
5
6
|
if (useAtomic) {
|
|
6
7
|
try {
|
|
@@ -9,19 +10,14 @@ export async function tryAcquireLock(storage, lockKey, ttlSeconds, useAtomic) {
|
|
|
9
10
|
const client = driver.getInstance?.();
|
|
10
11
|
if (client) {
|
|
11
12
|
const prefixedKey = base + lockKey;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
"1",
|
|
15
|
-
"NX",
|
|
16
|
-
"EX",
|
|
17
|
-
ttlSeconds
|
|
18
|
-
);
|
|
13
|
+
const redisClient = client;
|
|
14
|
+
const result = await redisClient.set(prefixedKey, owner, "NX", "EX", ttlSeconds);
|
|
19
15
|
if (result === "OK") {
|
|
20
16
|
logger.debug('[mutex] atomic lock acquired for key "%s"', lockKey);
|
|
21
|
-
return true;
|
|
17
|
+
return { key: lockKey, owner, atomic: true, prefixedKey, client: redisClient };
|
|
22
18
|
}
|
|
23
19
|
logger.debug('[mutex] atomic lock held for key "%s" \u2014 skipping', lockKey);
|
|
24
|
-
return
|
|
20
|
+
return null;
|
|
25
21
|
}
|
|
26
22
|
logger.warn("[mutex] atomic lock unavailable (getInstance not present on driver) \u2014 falling back to optimistic");
|
|
27
23
|
} catch (err) {
|
|
@@ -32,8 +28,26 @@ export async function tryAcquireLock(storage, lockKey, ttlSeconds, useAtomic) {
|
|
|
32
28
|
const lockExists = await storage.hasItem(lockKey);
|
|
33
29
|
if (lockExists) {
|
|
34
30
|
logger.debug('[mutex] optimistic lock held for key "%s" \u2014 skipping', lockKey);
|
|
35
|
-
return
|
|
31
|
+
return null;
|
|
36
32
|
}
|
|
37
|
-
await storage.setItem(lockKey,
|
|
38
|
-
return
|
|
33
|
+
await storage.setItem(lockKey, owner, { ttl: ttlSeconds });
|
|
34
|
+
return { key: lockKey, owner, atomic: false };
|
|
35
|
+
}
|
|
36
|
+
export async function releaseLock(storage, lease) {
|
|
37
|
+
const logger = useServerLogger({ tag: "authts-mutex" });
|
|
38
|
+
if (lease.atomic && lease.client?.eval && lease.prefixedKey) {
|
|
39
|
+
await lease.client.eval(
|
|
40
|
+
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end",
|
|
41
|
+
1,
|
|
42
|
+
lease.prefixedKey,
|
|
43
|
+
lease.owner
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const currentOwner = await storage.getItem(lease.key);
|
|
48
|
+
if (currentOwner === lease.owner) {
|
|
49
|
+
await storage.removeItem(lease.key);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
logger.debug('[mutex] skip releasing lock "%s" because owner changed', lease.key);
|
|
39
53
|
}
|
|
@@ -133,6 +133,7 @@ export default function NuxtAuthtsHandler(options) {
|
|
|
133
133
|
clientSecret: openid.clientSecret,
|
|
134
134
|
redirectUri: openid.redirectUri,
|
|
135
135
|
scopes: openid.scopes,
|
|
136
|
+
sessionSecret: sessionConfig.secret || "",
|
|
136
137
|
sessionStorageName: sessionConfig.storage.name,
|
|
137
138
|
sessionStoragePrefix: sessionConfig.storage.prefix
|
|
138
139
|
});
|
|
@@ -44,7 +44,13 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
44
44
|
clientId,
|
|
45
45
|
clientSecret,
|
|
46
46
|
tokenRefreshBufferMs,
|
|
47
|
-
distributedLock
|
|
47
|
+
distributedLock,
|
|
48
|
+
sessionCreateOptions: {
|
|
49
|
+
storageName,
|
|
50
|
+
storagePrefix,
|
|
51
|
+
expiresIn,
|
|
52
|
+
secret
|
|
53
|
+
}
|
|
48
54
|
});
|
|
49
55
|
if (Object.keys(sessionUpdate).length > 0) {
|
|
50
56
|
session = await updateSession(session.id, sessionUpdate, { storageName, storagePrefix, expiresIn, secret });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devcoffee/nuxt-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -41,8 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
43
43
|
"dist",
|
|
44
|
-
"CHANGELOG.md"
|
|
45
|
-
"GUIDELINE.md"
|
|
44
|
+
"CHANGELOG.md"
|
|
46
45
|
],
|
|
47
46
|
"nuxt": {
|
|
48
47
|
"module": "src/module.ts"
|
|
@@ -53,7 +52,7 @@
|
|
|
53
52
|
"cleanup": "nuxi cleanup && nuxi cleanup playground",
|
|
54
53
|
"dev:build": "nuxi build playground",
|
|
55
54
|
"prepack": "nuxt-module-build build",
|
|
56
|
-
"release": "npm run lint && npm run test:all && npm run prepack && npm publish && git push --follow-tags",
|
|
55
|
+
"release": "npm run lint && npm run test:all && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
57
56
|
"lint": "eslint",
|
|
58
57
|
"lint:fix": "eslint --fix",
|
|
59
58
|
"test": "cross-env NODE_OPTIONS=--no-deprecation vitest run test/unit",
|
package/GUIDELINE.md
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
# @devcoffee/nuxt-core — Contributor Guide
|
|
2
|
-
|
|
3
|
-
This guide is for developers working on the module itself. If you are integrating the module into your app, see [README.md](./README.md).
|
|
4
|
-
|
|
5
|
-
## Quick Reference
|
|
6
|
-
|
|
7
|
-
| Topic | Section |
|
|
8
|
-
|---|---|
|
|
9
|
-
| Module layers and responsibilities | [Architecture](#architecture) |
|
|
10
|
-
| Annotated file tree | [Directory Reference](#directory-reference) |
|
|
11
|
-
| First-time setup | [Development Setup](#development-setup) |
|
|
12
|
-
| Test commands | [Test Infrastructure](#test-infrastructure) |
|
|
13
|
-
| Adding external dependencies | [Adapter Pattern](#adapter-pattern) |
|
|
14
|
-
| Per-request auth flow | [Request Lifecycle](#request-lifecycle) |
|
|
15
|
-
| Guarded design choices | [Key Architectural Decisions](#key-architectural-decisions) |
|
|
16
|
-
| Publishing a new version | [Release Pipeline](#release-pipeline) |
|
|
17
|
-
| Planning workflow | [GSD Workflow](#gsd-workflow) |
|
|
18
|
-
| Style and naming rules | [Coding Conventions](#coding-conventions) |
|
|
19
|
-
|
|
20
|
-
## Architecture
|
|
21
|
-
|
|
22
|
-
The module is organized into five layers. Each layer has a single responsibility and strict import rules.
|
|
23
|
-
|
|
24
|
-
### Layer 1: Module Definition (`src/module.ts`)
|
|
25
|
-
|
|
26
|
-
Entry point. Configures and registers all runtime components into Nuxt and Nitro during build:
|
|
27
|
-
|
|
28
|
-
- Registers server plugins, server composables, and server handler imports via `@nuxt/kit`
|
|
29
|
-
- Registers app plugins, app composables, and route middleware
|
|
30
|
-
- Injects runtime configuration from `nuxtCore` options
|
|
31
|
-
- Sets up Nitro storage and DevTools routes
|
|
32
|
-
|
|
33
|
-
### Layer 2: Server / Nitro (`src/runtime/server/`)
|
|
34
|
-
|
|
35
|
-
All server-side auth logic. Runs in the Nitro runtime (not the browser):
|
|
36
|
-
|
|
37
|
-
- `plugins/authts.ts` — global Nitro plugin; validates and refreshes sessions on every HTTP request
|
|
38
|
-
- `core/helpers.ts` — PKCE, state, token exchange, session CRUD, token refresh mutex
|
|
39
|
-
- `core/nuxtAuthtsHandler.ts` — `NuxtAuthtsHandler` export; handles GET_SESSION, TOKEN, LOGOUT, AUTHORIZE_URL actions
|
|
40
|
-
- `core/nuxtForwardHandler.ts` — `NuxtForwardRequestHandler` export; authenticated API proxy
|
|
41
|
-
- `adapters/` — external dependency wrappers (see [Adapter Pattern](#adapter-pattern))
|
|
42
|
-
- `composables/useServerLogger.ts` — server-side logging composable
|
|
43
|
-
|
|
44
|
-
### Layer 3: Client / App (`src/runtime/app/`)
|
|
45
|
-
|
|
46
|
-
Client-side auth state, UI interactions, and route protection. Runs in Vue/Nuxt:
|
|
47
|
-
|
|
48
|
-
- `plugins/authts.ts` — app plugin; initializes session state, provides `$sessionContext`, `$sessionReady`, fires hooks
|
|
49
|
-
- `middleware/authts.ts` — global route middleware; enforces `definePageMeta` auth requirements
|
|
50
|
-
- `composables/useAuthContext.ts` — reactive auth state and actions
|
|
51
|
-
- `composables/useSessionContext.ts` — low-level session accessor
|
|
52
|
-
- `composables/useLogger.ts` — client-side logging composable
|
|
53
|
-
- `pages/authorize.vue` — OIDC callback page (auto-registered at `openid.redirectUri`)
|
|
54
|
-
|
|
55
|
-
### Layer 4: Types (`src/types/`)
|
|
56
|
-
|
|
57
|
-
All TypeScript interfaces and augmentations:
|
|
58
|
-
|
|
59
|
-
- `types/authts.d.ts` — auth types (`SessionContext`, `AuthorizedUser`, `ModuleOptions`, etc.)
|
|
60
|
-
- `types/logging.d.ts` — logging types
|
|
61
|
-
- `types/index.d.ts` — central re-export point; consumers import from `@devcoffee/nuxt-core`
|
|
62
|
-
|
|
63
|
-
### Layer 5: Module Helpers (`src/helpers.ts`)
|
|
64
|
-
|
|
65
|
-
Option normalization and public config sanitization:
|
|
66
|
-
|
|
67
|
-
- `normalizedModuleOptions()` — merges user config with defaults
|
|
68
|
-
- `normalizePublicRuntimeConfig()` — strips private fields before injecting into runtime config
|
|
69
|
-
|
|
70
|
-
## Directory Reference
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
src/
|
|
74
|
-
├── module.ts # Layer 1: Module Definition
|
|
75
|
-
├── helpers.ts # Layer 5: Option normalization + defaults
|
|
76
|
-
├── utils.ts # Utility relay (re-exports from runtime/utils)
|
|
77
|
-
├── types/
|
|
78
|
-
│ ├── index.d.ts # Central type re-export for consumers
|
|
79
|
-
│ ├── authts.d.ts # Auth types (SessionContext, AuthorizedUser, etc.)
|
|
80
|
-
│ └── logging.d.ts # Logging types
|
|
81
|
-
└── runtime/
|
|
82
|
-
├── server/ # Layer 2: Server / Nitro
|
|
83
|
-
│ ├── adapters/ # External dependency wrappers
|
|
84
|
-
│ │ ├── http.ts # h3 cookie, request, response, error functions
|
|
85
|
-
│ │ ├── oidc.ts # openid-client wrappers with module-owned types
|
|
86
|
-
│ │ ├── storage.ts # Nitro useStorage session CRUD
|
|
87
|
-
│ │ └── utils.ts # deepMerge, omit, pick (native, no lodash)
|
|
88
|
-
│ ├── core/ # Business logic — imports ONLY from adapters/
|
|
89
|
-
│ │ ├── helpers.ts
|
|
90
|
-
│ │ ├── nuxtAuthtsHandler.ts
|
|
91
|
-
│ │ └── nuxtForwardHandler.ts
|
|
92
|
-
│ ├── composables/
|
|
93
|
-
│ │ └── useServerLogger.ts
|
|
94
|
-
│ ├── plugins/
|
|
95
|
-
│ │ └── authts.ts # Per-request session validation entry point
|
|
96
|
-
│ └── dev/ # DevTools route handler
|
|
97
|
-
└── app/ # Layer 3: Client / App
|
|
98
|
-
├── composables/
|
|
99
|
-
│ ├── useAuthContext.ts
|
|
100
|
-
│ ├── useSessionContext.ts
|
|
101
|
-
│ └── useLogger.ts
|
|
102
|
-
├── middleware/
|
|
103
|
-
│ └── authts.ts # Global route protection middleware
|
|
104
|
-
├── pages/
|
|
105
|
-
│ └── authorize.vue # OIDC callback page (auto-registered)
|
|
106
|
-
├── plugins/
|
|
107
|
-
│ ├── authts.ts # Session init, hooks, $sessionReady
|
|
108
|
-
│ ├── logging.ts
|
|
109
|
-
│ ├── formatters.ts
|
|
110
|
-
│ └── locale.ts
|
|
111
|
-
└── utils/
|
|
112
|
-
└── utils.ts # Utility relay
|
|
113
|
-
|
|
114
|
-
test/
|
|
115
|
-
├── unit/ # Vitest unit tests
|
|
116
|
-
└── e2e/ # Playwright + Vitest E2E tests
|
|
117
|
-
├── fixture/ # Nuxt test app with mock OIDC server
|
|
118
|
-
└── *.test.ts
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Development Setup
|
|
122
|
-
|
|
123
|
-
### Prerequisites
|
|
124
|
-
|
|
125
|
-
- Node.js LTS (18+)
|
|
126
|
-
- npm 10+
|
|
127
|
-
- A `.certs/devcoffee.ca.pem` file — required for TLS in development (internal CA). Place the DevCoffee CA certificate at this path.
|
|
128
|
-
|
|
129
|
-
### First-time setup
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
# 1. Clone and install
|
|
133
|
-
git clone <repo-url>
|
|
134
|
-
cd devcoffee-nuxt-core
|
|
135
|
-
npm install
|
|
136
|
-
|
|
137
|
-
# 2. Generate type stubs (required before dev or test)
|
|
138
|
-
npm run dev:prepare
|
|
139
|
-
|
|
140
|
-
# 3. Start the playground dev server
|
|
141
|
-
npm run dev
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
The playground runs at `http://localhost:3000`. The custom CA certificate is injected via `NODE_EXTRA_CA_CERTS` in the `dev` script.
|
|
145
|
-
|
|
146
|
-
### Clean rebuild
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
npm run cleanup # Remove .nuxt and playground build artifacts
|
|
150
|
-
npm run dev:prepare
|
|
151
|
-
npm run dev
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## Test Infrastructure
|
|
155
|
-
|
|
156
|
-
| Command | What it runs | When to use |
|
|
157
|
-
|---|---|---|
|
|
158
|
-
| `npm run test` | Vitest unit suite (once) | Before committing |
|
|
159
|
-
| `npm run test:watch` | Vitest unit suite (watch mode) | During development |
|
|
160
|
-
| `npm run test:types` | `vue-tsc --noEmit` type check | Before committing |
|
|
161
|
-
| `npm run test:e2e` | Playwright + Vitest E2E tests (headless) | After changing auth flow, middleware, or session handling |
|
|
162
|
-
| `npm run test:e2e:ui` | E2E tests in headed Chromium (via `PLAYWRIGHT_HEADLESS=false`) | Debugging browser-mode E2E failures |
|
|
163
|
-
| `npm run test:all` | All unit + E2E tests combined | Before opening a PR or releasing |
|
|
164
|
-
|
|
165
|
-
### Unit tests (`test/unit/`)
|
|
166
|
-
|
|
167
|
-
Written with Vitest. Use `@nuxt/test-utils` for composable and middleware tests. Mock Nitro virtual modules where needed (e.g., `vi.mock('nitropack/runtime')`).
|
|
168
|
-
|
|
169
|
-
Tests follow source-text assertions for structural code presence and behavior assertions for logic. TDD was used for SEC-*, PERF-*, MDLW-*, and SSR-* requirements — new security or behavior requirements should follow the same pattern.
|
|
170
|
-
|
|
171
|
-
### E2E tests (`test/e2e/`)
|
|
172
|
-
|
|
173
|
-
Run in Vitest but use `@playwright/test` for browser automation and `createNuxtDevServer` from `@nuxt/test-utils` to spin up the fixture Nuxt app. A mock OIDC server is embedded as a Nitro route handler in the fixture.
|
|
174
|
-
|
|
175
|
-
Key files:
|
|
176
|
-
|
|
177
|
-
- `test/e2e/fixture/` — test Nuxt app (standalone, not the playground)
|
|
178
|
-
- `test/e2e/fixture/server/routes/` — mock OIDC discovery, token, and userinfo endpoints
|
|
179
|
-
- `test/e2e/middleware.test.ts` — E2E-01 (required redirect) and E2E-02 (unauthenticatedOnly)
|
|
180
|
-
- `test/e2e/session.test.ts` — E2E-03 (session creation) and E2E-07 (token refresh)
|
|
181
|
-
- `test/e2e/auth-flow.test.ts` — E2E-04 (valid TOKEN flow), E2E-05 (invalid state), E2E-06 (LOGOUT)
|
|
182
|
-
- `test/e2e/auth-flow-browser.test.ts` — E2E-08 (browser-mode Chromium tests)
|
|
183
|
-
|
|
184
|
-
**Important:** Browser tests (`auth-flow-browser.test.ts`) are isolated in their own file to prevent `EADDRINUSE` port conflicts when two Nuxt dev servers start in the same process.
|
|
185
|
-
|
|
186
|
-
## Adapter Pattern
|
|
187
|
-
|
|
188
|
-
### Why adapters exist
|
|
189
|
-
|
|
190
|
-
Business logic in `src/runtime/server/core/` must not import directly from `h3`, `openid-client`, or any external npm package. Instead, all external calls go through adapter barrel files in `src/runtime/server/adapters/`.
|
|
191
|
-
|
|
192
|
-
**Rationale:** Adapters create a stable internal boundary. If `h3` or `openid-client` upgrades break their API, only the adapter needs to change — not every call site in the business logic. Adapters also own the type bridge between external library types and module-owned types (e.g., `OidcUserInfo`).
|
|
193
|
-
|
|
194
|
-
This rule is enforced by grep assertions in the test suite:
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
# These must return zero matches:
|
|
198
|
-
grep -r "from 'h3'" src/runtime/server/core/
|
|
199
|
-
grep -r "from 'openid-client'" src/runtime/server/core/
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### How to add a new external dependency
|
|
203
|
-
|
|
204
|
-
1. Install the package as a dependency
|
|
205
|
-
2. Create (or update) the appropriate adapter file in `src/runtime/server/adapters/`
|
|
206
|
-
3. Write module-owned types for any types the adapter exposes
|
|
207
|
-
4. Export only what business logic needs — keep the adapter surface minimal
|
|
208
|
-
5. Import from the adapter in `core/` files, never directly from the package
|
|
209
|
-
|
|
210
|
-
Example: adding a hypothetical `jose` utility to the http adapter:
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
// src/runtime/server/adapters/http.ts
|
|
214
|
-
import { decodeJwt as _decodeJwt } from 'jose'
|
|
215
|
-
import type { JWTPayload } from './types' // module-owned type, not jose's type
|
|
216
|
-
|
|
217
|
-
export function decodeJwt(token: string): JWTPayload {
|
|
218
|
-
return _decodeJwt(token) as JWTPayload
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## Request Lifecycle
|
|
223
|
-
|
|
224
|
-
Every HTTP request to the Nuxt app passes through this sequence:
|
|
225
|
-
|
|
226
|
-
```
|
|
227
|
-
HTTP request
|
|
228
|
-
│
|
|
229
|
-
▼
|
|
230
|
-
Nitro server plugin (src/runtime/server/plugins/authts.ts)
|
|
231
|
-
│ Reads session cookie → validateSession()
|
|
232
|
-
│ If authenticated + near-expiry → refreshTokenIfNeeded() [mutex-protected per session]
|
|
233
|
-
│ Sets event.context.sessionId
|
|
234
|
-
│
|
|
235
|
-
▼
|
|
236
|
-
Route handler (src/runtime/server/core/nuxtAuthtsHandler.ts)
|
|
237
|
-
│ Reads action from URL path: GET_SESSION | TOKEN | LOGOUT | AUTHORIZE_URL
|
|
238
|
-
│ Calls getSession(event.context.sessionId) → reads + decrypts session from storage
|
|
239
|
-
│ Dispatches to action handler
|
|
240
|
-
│
|
|
241
|
-
▼
|
|
242
|
-
Client (src/runtime/app/plugins/authts.ts)
|
|
243
|
-
│ useAsyncData('authts:session') fetches /api/_auth/session on SSR
|
|
244
|
-
│ Sets $sessionReady promise — resolved before route middleware runs
|
|
245
|
-
│
|
|
246
|
-
▼
|
|
247
|
-
Route middleware (src/runtime/app/middleware/authts.ts)
|
|
248
|
-
│ Awaits $sessionReady
|
|
249
|
-
│ Reads definePageMeta({ authts: { required, unauthenticatedOnly } })
|
|
250
|
-
│ Redirects or aborts navigation based on auth state
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### Authorization code flow (login)
|
|
254
|
-
|
|
255
|
-
```
|
|
256
|
-
User clicks login
|
|
257
|
-
│
|
|
258
|
-
▼
|
|
259
|
-
useAuthContext.login(redirectTo)
|
|
260
|
-
│ GET /api/_auth/authorize-url → returns OIDC authorization URL
|
|
261
|
-
│ Stores redirectTo in session cookie (auths.redirect)
|
|
262
|
-
│
|
|
263
|
-
▼
|
|
264
|
-
Browser redirects to OIDC provider
|
|
265
|
-
│
|
|
266
|
-
▼
|
|
267
|
-
Provider redirects to /authorize?code=...&state=...
|
|
268
|
-
│
|
|
269
|
-
▼
|
|
270
|
-
authorize.vue (auto-registered OIDC callback page)
|
|
271
|
-
│ useAuthContext.authorize(code, state)
|
|
272
|
-
│ POST /api/_auth/token → validates state, exchanges code for tokens
|
|
273
|
-
│ If autoFetchUser: fetches userinfo from provider
|
|
274
|
-
│ Stores session in Nitro storage (tokenSet encrypted if secret is set)
|
|
275
|
-
│ Fires user:loggedIn hook
|
|
276
|
-
│
|
|
277
|
-
▼
|
|
278
|
-
Browser redirects to intended destination
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
## Key Architectural Decisions
|
|
282
|
-
|
|
283
|
-
These decisions are enforced by tests or type constraints. Changing them requires updating the tests that guard them.
|
|
284
|
-
|
|
285
|
-
| Decision | What it means |
|
|
286
|
-
|---|---|
|
|
287
|
-
| `sessions.secret` empty = no signing | Backward compatible — existing deployments without a secret continue working. Insecure for new deployments. |
|
|
288
|
-
| HKDF-SHA256 derives AES key from `sessions.secret` | Domain-separated with `'devcoffee-authts-tokenset-v1'` info string |
|
|
289
|
-
| Cookie names are `auths.ssid`, `auths.state`, `auths.pkce`, `auths.redirect` | Configured in `sessions.names` defaults in `src/helpers.ts` |
|
|
290
|
-
| `deleteSession()` is separate from `renewSession()` | Logout deletes the session and does not create a replacement. Prevents zombie sessions. |
|
|
291
|
-
| Token refresh mutex per session | Lock is placed inside `accessExpired && refreshToken` branch only — no storage overhead for non-expiring tokens |
|
|
292
|
-
| Adapter import enforcement | Verified by grep in test suite. Zero direct `h3` or `openid-client` imports allowed in `core/`. |
|
|
293
|
-
| `usePkce: false` in E2E fixture | PKCE threading through test requires additional cookie management. PKCE is fully covered at the unit layer. |
|
|
294
|
-
|
|
295
|
-
Full decision log: see `## Decisions` in `.planning/STATE.md`.
|
|
296
|
-
|
|
297
|
-
## Release Pipeline
|
|
298
|
-
|
|
299
|
-
```bash
|
|
300
|
-
npm run release
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
This runs in sequence: `lint` → `test:all` → `prepack` → `changelogen` → `npm publish` → `git push --follow-tags`.
|
|
304
|
-
|
|
305
|
-
Individual steps:
|
|
306
|
-
|
|
307
|
-
```bash
|
|
308
|
-
npm run lint # ESLint check (with --fix)
|
|
309
|
-
npm run test # Vitest unit suite
|
|
310
|
-
npm run test:types # vue-tsc type check
|
|
311
|
-
npm run test:all # All unit + E2E tests combined
|
|
312
|
-
npm run prepack # Build module dist (ES module + .d.ts types via @nuxt/module-builder)
|
|
313
|
-
npm run release # Full pipeline (lint + test:all + prepack + changelogen + publish + push)
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
The published package is `@devcoffee/nuxt-core`. The `dist/` directory is generated by `@nuxt/module-builder` and is what consumers install.
|
|
317
|
-
|
|
318
|
-
## GSD Workflow
|
|
319
|
-
|
|
320
|
-
This project uses a phase-based planning system. All code changes must go through a GSD command to keep planning artifacts in sync.
|
|
321
|
-
|
|
322
|
-
| Task type | Entry point |
|
|
323
|
-
|---|---|
|
|
324
|
-
| Small fix or ad-hoc change | `/gsd:quick` |
|
|
325
|
-
| Bug investigation | `/gsd:debug` |
|
|
326
|
-
| Planned phase work | `/gsd:execute-phase` |
|
|
327
|
-
|
|
328
|
-
Planning artifacts live in `.planning/`:
|
|
329
|
-
|
|
330
|
-
- `ROADMAP.md` — all phases and their requirements
|
|
331
|
-
- `STATE.md` — current position, accumulated decisions, blockers
|
|
332
|
-
- `phases/NN-slug/` — per-phase RESEARCH.md, PLAN.md files, SUMMARY.md files
|
|
333
|
-
|
|
334
|
-
Do not make direct file edits outside a GSD workflow unless explicitly bypassing it.
|
|
335
|
-
|
|
336
|
-
## Coding Conventions
|
|
337
|
-
|
|
338
|
-
Key rules (full reference: `CLAUDE.md`):
|
|
339
|
-
|
|
340
|
-
- **No semicolons** — Prettier enforces this
|
|
341
|
-
- **Single quotes** for strings
|
|
342
|
-
- **2-space indent**, 120-char line length
|
|
343
|
-
- **Trailing commas:** es5 style (objects and arrays, not function parameters)
|
|
344
|
-
- **TypeScript strict** — no `any` in public API surface
|
|
345
|
-
- **Composables:** `use[Name].ts` file and function name
|
|
346
|
-
- **Server handlers:** `Nuxt[Name]Handler` (PascalCase with `Nuxt` prefix)
|
|
347
|
-
- **Getters:** `get[Name]`, **Setters:** `set[Name]`, **Validators:** `validate[Name]`
|
|
348
|
-
- **Logging:** Use `useLogger` on client, `useServerLogger` on server. Always include `tag` and `level`.
|
|
349
|
-
- **Error handling:** `createError()` from h3 for server errors, `abortNavigation(createError())` for middleware
|
|
350
|
-
- **Imports:** Prefer named imports. Use `#imports` for Nuxt auto-imports.
|
|
351
|
-
- **JSDoc:** All public functions and composables must have JSDoc with `@param`, `@returns`, `@example`, `@since 1.0.0`
|