sumor 2.0.2 → 3.0.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.
Files changed (216) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +467 -0
  3. package/dist/server/controllers/getAuthorizeUrlController.d.ts +9 -0
  4. package/dist/server/controllers/getAuthorizeUrlController.d.ts.map +1 -0
  5. package/dist/server/controllers/getAuthorizeUrlController.js +79 -0
  6. package/dist/server/controllers/getAuthorizeUrlController.js.map +1 -0
  7. package/dist/server/controllers/infoController.d.ts +12 -0
  8. package/dist/server/controllers/infoController.d.ts.map +1 -0
  9. package/dist/server/controllers/infoController.js +82 -0
  10. package/dist/server/controllers/infoController.js.map +1 -0
  11. package/dist/server/controllers/logoutController.d.ts +12 -0
  12. package/dist/server/controllers/logoutController.d.ts.map +1 -0
  13. package/dist/server/controllers/logoutController.js +84 -0
  14. package/dist/server/controllers/logoutController.js.map +1 -0
  15. package/dist/server/controllers/oauthCallbackController.d.ts +9 -0
  16. package/dist/server/controllers/oauthCallbackController.d.ts.map +1 -0
  17. package/dist/server/controllers/oauthCallbackController.js +118 -0
  18. package/dist/server/controllers/oauthCallbackController.js.map +1 -0
  19. package/dist/server/controllers/tokenRefreshController.d.ts +9 -0
  20. package/dist/server/controllers/tokenRefreshController.d.ts.map +1 -0
  21. package/dist/server/controllers/tokenRefreshController.js +58 -0
  22. package/dist/server/controllers/tokenRefreshController.js.map +1 -0
  23. package/dist/server/index.d.ts +15 -0
  24. package/dist/server/index.d.ts.map +1 -0
  25. package/dist/server/index.js +56 -0
  26. package/dist/server/index.js.map +1 -0
  27. package/dist/server/middlewares/loadJwtUserMiddleware.d.ts +26 -0
  28. package/dist/server/middlewares/loadJwtUserMiddleware.d.ts.map +1 -0
  29. package/dist/server/middlewares/loadJwtUserMiddleware.js +64 -0
  30. package/dist/server/middlewares/loadJwtUserMiddleware.js.map +1 -0
  31. package/dist/server/models/blacklistModel.d.ts +63 -0
  32. package/dist/server/models/blacklistModel.d.ts.map +1 -0
  33. package/dist/server/models/blacklistModel.js +133 -0
  34. package/dist/server/models/blacklistModel.js.map +1 -0
  35. package/dist/server/models/jwksModel.d.ts +21 -0
  36. package/dist/server/models/jwksModel.d.ts.map +1 -0
  37. package/dist/server/models/jwksModel.js +52 -0
  38. package/dist/server/models/jwksModel.js.map +1 -0
  39. package/dist/server/models/tokenModel.d.ts +36 -0
  40. package/dist/server/models/tokenModel.d.ts.map +1 -0
  41. package/dist/server/models/tokenModel.js +83 -0
  42. package/dist/server/models/tokenModel.js.map +1 -0
  43. package/dist/server/routes.d.ts +7 -0
  44. package/dist/server/routes.d.ts.map +1 -0
  45. package/dist/server/routes.js +51 -0
  46. package/dist/server/routes.js.map +1 -0
  47. package/dist/server/services/oauthService.d.ts +157 -0
  48. package/dist/server/services/oauthService.d.ts.map +1 -0
  49. package/dist/server/services/oauthService.js +356 -0
  50. package/dist/server/services/oauthService.js.map +1 -0
  51. package/dist/server/types/blacklist.d.ts +11 -0
  52. package/dist/server/types/blacklist.d.ts.map +1 -0
  53. package/dist/server/types/blacklist.js +6 -0
  54. package/dist/server/types/blacklist.js.map +1 -0
  55. package/dist/server/types/oauth.d.ts +123 -0
  56. package/dist/server/types/oauth.d.ts.map +1 -0
  57. package/dist/server/types/oauth.js +6 -0
  58. package/dist/server/types/oauth.js.map +1 -0
  59. package/dist/server/utils/authUtils.d.ts +15 -0
  60. package/dist/server/utils/authUtils.d.ts.map +1 -0
  61. package/dist/server/utils/authUtils.js +46 -0
  62. package/dist/server/utils/authUtils.js.map +1 -0
  63. package/dist/server/utils/config.d.ts +8 -0
  64. package/dist/server/utils/config.d.ts.map +1 -0
  65. package/dist/server/utils/config.js +45 -0
  66. package/dist/server/utils/config.js.map +1 -0
  67. package/dist/server/utils/http.d.ts +11 -0
  68. package/dist/server/utils/http.d.ts.map +1 -0
  69. package/dist/server/utils/http.js +37 -0
  70. package/dist/server/utils/http.js.map +1 -0
  71. package/dist/server/utils/oauthTokenUtils.d.ts +10 -0
  72. package/dist/server/utils/oauthTokenUtils.d.ts.map +1 -0
  73. package/dist/server/utils/oauthTokenUtils.js +22 -0
  74. package/dist/server/utils/oauthTokenUtils.js.map +1 -0
  75. package/dist/web/Sumor.d.ts +61 -0
  76. package/dist/web/Sumor.d.ts.map +1 -0
  77. package/dist/web/Sumor.js +207 -0
  78. package/dist/web/Sumor.js.map +1 -0
  79. package/dist/web/index.d.ts +6 -0
  80. package/dist/web/index.d.ts.map +1 -0
  81. package/dist/web/index.js +14 -0
  82. package/dist/web/index.js.map +1 -0
  83. package/package.json +65 -65
  84. package/i18n/README.md +0 -32
  85. package/i18n/sumor_api.yaml +0 -16
  86. package/i18n/sumor_app.yaml +0 -3
  87. package/i18n/sumor_internal.yaml +0 -1
  88. package/i18n/sumor_token.yaml +0 -2
  89. package/i18nExt/sumor_api.de.yaml +0 -3
  90. package/i18nExt/sumor_api.en.yaml +0 -3
  91. package/i18nExt/sumor_api.es.yaml +0 -3
  92. package/i18nExt/sumor_api.fr.yaml +0 -3
  93. package/i18nExt/sumor_api.it.yaml +0 -3
  94. package/i18nExt/sumor_api.ja.yaml +0 -3
  95. package/i18nExt/sumor_api.ko.yaml +0 -3
  96. package/i18nExt/sumor_api.pt.yaml +0 -3
  97. package/i18nExt/sumor_api.zh-TW.yaml +0 -3
  98. package/i18nExt/sumor_api.zh.yaml +0 -3
  99. package/i18nExt/sumor_app.de.yaml +0 -3
  100. package/i18nExt/sumor_app.en.yaml +0 -3
  101. package/i18nExt/sumor_app.es.yaml +0 -3
  102. package/i18nExt/sumor_app.zh-TW.yaml +0 -3
  103. package/i18nExt/sumor_app.zh.yaml +0 -3
  104. package/i18nExt/sumor_internal.de.yaml +0 -1
  105. package/i18nExt/sumor_internal.en.yaml +0 -1
  106. package/i18nExt/sumor_internal.es.yaml +0 -1
  107. package/i18nExt/sumor_internal.zh-TW.yaml +0 -1
  108. package/i18nExt/sumor_internal.zh.yaml +0 -1
  109. package/i18nExt/sumor_token.de.yaml +0 -2
  110. package/i18nExt/sumor_token.es.yaml +0 -2
  111. package/i18nExt/sumor_token.fr.yaml +0 -2
  112. package/i18nExt/sumor_token.it.yaml +0 -2
  113. package/i18nExt/sumor_token.ja.yaml +0 -2
  114. package/i18nExt/sumor_token.ko.yaml +0 -2
  115. package/i18nExt/sumor_token.pt.yaml +0 -2
  116. package/i18nExt/sumor_token.ru.yaml +0 -2
  117. package/i18nExt/sumor_token.tr.yaml +0 -2
  118. package/i18nExt/sumor_token.zh-TW.yaml +0 -2
  119. package/i18nExt/sumor_token.zh.yaml +0 -2
  120. package/modules/alertPage/html/template.html +0 -162
  121. package/modules/alertPage/html/undraw_access-denied.svg +0 -52
  122. package/modules/alertPage/html/undraw_alert.svg +0 -1
  123. package/modules/alertPage/html/undraw_celebration.svg +0 -78
  124. package/modules/alertPage/html/undraw_complete-form.svg +0 -1
  125. package/modules/alertPage/html/undraw_login.svg +0 -1
  126. package/modules/alertPage/index.js +0 -46
  127. package/modules/alertPage/index.test.js +0 -12
  128. package/modules/i18n/README.md +0 -90
  129. package/modules/i18n/convertI18nValue/README.md +0 -31
  130. package/modules/i18n/convertI18nValue/getI18nTemplate.js +0 -26
  131. package/modules/i18n/convertI18nValue/getI18nTemplate.test.js +0 -40
  132. package/modules/i18n/convertI18nValue/index.js +0 -14
  133. package/modules/i18n/convertI18nValue/index.test.js +0 -55
  134. package/modules/i18n/convertI18nValue/stringVariableReplace.js +0 -13
  135. package/modules/i18n/convertI18nValue/stringVariableReplace.test.js +0 -39
  136. package/modules/i18n/index.js +0 -26
  137. package/modules/i18n/index.test.js +0 -13
  138. package/modules/i18n/load/README.md +0 -28
  139. package/modules/i18n/load/load.js +0 -31
  140. package/modules/i18n/load/load.test.js +0 -30
  141. package/modules/i18n/registry.js +0 -48
  142. package/modules/i18n/registry.test.js +0 -84
  143. package/modules/logger/convert/parseFile.js +0 -14
  144. package/modules/logger/convert/parseFile.test.js +0 -28
  145. package/modules/logger/convert/stringifyCMD.js +0 -48
  146. package/modules/logger/convert/stringifyCMD.test.js +0 -37
  147. package/modules/logger/convert/stringifyFile.js +0 -24
  148. package/modules/logger/convert/stringifyFile.test.js +0 -79
  149. package/modules/logger/index.js +0 -82
  150. package/modules/logger/index.test.js +0 -124
  151. package/modules/logger/logFileOperator.js +0 -50
  152. package/modules/logger/logFileOperator.test.js +0 -69
  153. package/modules/middlewares/apiMiddleware/errorCatcher.js +0 -9
  154. package/modules/middlewares/apiMiddleware/exposeApis/index.js +0 -82
  155. package/modules/middlewares/apiMiddleware/index.js +0 -111
  156. package/modules/middlewares/apiMiddleware/index.test.js +0 -145
  157. package/modules/middlewares/apiMiddleware/load/index.js +0 -35
  158. package/modules/middlewares/apiMiddleware/load/index.test.js +0 -30
  159. package/modules/middlewares/apiMiddleware/metadataToSwagger.js +0 -139
  160. package/modules/middlewares/apiMiddleware/prepareData/format/caseSensitive.js +0 -26
  161. package/modules/middlewares/apiMiddleware/prepareData/format/caseSensitive.test.js +0 -36
  162. package/modules/middlewares/apiMiddleware/prepareData/format/convertType.js +0 -48
  163. package/modules/middlewares/apiMiddleware/prepareData/format/convertType.test.js +0 -53
  164. package/modules/middlewares/apiMiddleware/prepareData/format/defaultValue.js +0 -31
  165. package/modules/middlewares/apiMiddleware/prepareData/format/defaultValue.test.js +0 -31
  166. package/modules/middlewares/apiMiddleware/prepareData/format/index.js +0 -18
  167. package/modules/middlewares/apiMiddleware/prepareData/format/index.test.js +0 -40
  168. package/modules/middlewares/apiMiddleware/prepareData/format/precision.js +0 -12
  169. package/modules/middlewares/apiMiddleware/prepareData/format/precision.test.js +0 -33
  170. package/modules/middlewares/apiMiddleware/prepareData/format/trim.js +0 -15
  171. package/modules/middlewares/apiMiddleware/prepareData/format/trim.test.js +0 -24
  172. package/modules/middlewares/apiMiddleware/prepareData/index.js +0 -29
  173. package/modules/middlewares/apiMiddleware/prepareData/index.test.js +0 -121
  174. package/modules/middlewares/apiMiddleware/prepareData/validate/checkLength.js +0 -26
  175. package/modules/middlewares/apiMiddleware/prepareData/validate/checkLength.test.js +0 -52
  176. package/modules/middlewares/apiMiddleware/prepareData/validate/index.js +0 -30
  177. package/modules/middlewares/apiMiddleware/public/favicon.ico +0 -0
  178. package/modules/middlewares/apiMiddleware/response/sendError.js +0 -57
  179. package/modules/middlewares/apiMiddleware/response/sendError.test.js +0 -251
  180. package/modules/middlewares/apiMiddleware/response/sendNotFound.js +0 -26
  181. package/modules/middlewares/apiMiddleware/response/sendResponse.js +0 -25
  182. package/modules/middlewares/apiMiddleware/response/sendSuccess.js +0 -30
  183. package/modules/middlewares/bodyMiddleware/cleanupFiles.js +0 -14
  184. package/modules/middlewares/bodyMiddleware/cleanupFiles.test.js +0 -54
  185. package/modules/middlewares/bodyMiddleware/fileParser.js +0 -69
  186. package/modules/middlewares/bodyMiddleware/fileParser.test.js +0 -163
  187. package/modules/middlewares/bodyMiddleware/index.js +0 -12
  188. package/modules/middlewares/bodyMiddleware/index.test.js +0 -64
  189. package/modules/middlewares/bodyMiddleware/mergeData.js +0 -4
  190. package/modules/middlewares/bodyMiddleware/mergeData.test.js +0 -38
  191. package/modules/middlewares/i18nMiddleware/index.js +0 -82
  192. package/modules/middlewares/i18nMiddleware/index.test.js +0 -75
  193. package/modules/middlewares/tokenMiddleware/Token.js +0 -115
  194. package/modules/middlewares/tokenMiddleware/Token.test.js +0 -67
  195. package/modules/middlewares/tokenMiddleware/index.js +0 -32
  196. package/modules/middlewares/tokenMiddleware/index.test.js +0 -115
  197. package/modules/middlewares/tokenMiddleware/parseCookie.js +0 -24
  198. package/modules/middlewares/tokenMiddleware/parseCookie.test.js +0 -49
  199. package/modules/serve/formatConfig.js +0 -7
  200. package/modules/serve/formatConfig.test.js +0 -28
  201. package/modules/serve/index.js +0 -30
  202. package/modules/serve/index.test.js +0 -69
  203. package/modules/serve/listenApp.js +0 -11
  204. package/modules/system/getSystemLanguage.js +0 -9
  205. package/modules/system/getSystemLanguage.test.js +0 -19
  206. package/modules/utils/getError.js +0 -56
  207. package/modules/utils/getError.test.js +0 -97
  208. package/modules/utils/loadConfig.js +0 -73
  209. package/modules/utils/loadConfig.test.js +0 -129
  210. package/modules/utils/pathUtils.js +0 -43
  211. package/modules/utils/pathUtils.test.js +0 -52
  212. package/modules/utils/type.js +0 -14
  213. package/modules/utils/type.test.js +0 -40
  214. package/src/app.js +0 -40
  215. package/src/cli.js +0 -28
  216. package/src/index.js +0 -3
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Sumor Cloud
3
+ Copyright (c) 2024 Lycoo
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md ADDED
@@ -0,0 +1,467 @@
1
+ # Sumor - OAuth Authentication Framework
2
+
3
+ [![npm version](https://img.shields.io/npm/v/sumor.svg)](https://www.npmjs.com/package/sumor)
4
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+
6
+ A comprehensive OAuth 2.0 authentication framework for Express.js applications with role-based access control (RBAC). Sumor simplifies OAuth integration, token management, and permission-based route protection in multi-service architectures.
7
+
8
+ ## ✨ Key Features
9
+
10
+ - **🔐 OAuth 2.0 Complete Flow**: Full authorization code flow with PKCE support
11
+ - **🔑 Session & Token Management**: Secure token exchange, refresh, and blacklisting
12
+ - **🛡️ JWT Verification**: Built-in JWT validation using JWKS (JSON Web Key Set)
13
+ - **👥 Role-Based Access Control (RBAC)**: Permission-based route protection and middleware
14
+ - **📝 TypeScript First**: Full TypeScript support with complete type definitions
15
+ - **🚀 Express Integration**: Drop-in middleware and route setup
16
+ - **🎯 Permission Sync**: Automatic permission synchronization with OAuth provider
17
+ - **💾 Session Revocation**: Token blacklist support for logout and session management
18
+ - **🌐 Multi-Domain Support**: Built-in domain and origin handling
19
+ - **⚡ Request Context**: Access user info and OAuth service in Express request object
20
+
21
+ ## 📦 Installation
22
+
23
+ ```bash
24
+ npm install sumor
25
+ ```
26
+
27
+ ## 🚀 Quick Start
28
+
29
+ ### Basic Setup
30
+
31
+ ```typescript
32
+ import express from 'express'
33
+ import setupSumor from 'sumor'
34
+
35
+ const app = express()
36
+
37
+ // Define permissions for your application
38
+ // Format: <module>:<operation>
39
+ // Operations: view, create, edit, delete
40
+ const permissions = {
41
+ permissions: [
42
+ 'users:view', // View user information
43
+ 'users:edit', // Edit users
44
+ 'posts:view', // View posts
45
+ 'posts:create', // Create posts
46
+ 'posts:edit', // Edit posts
47
+ 'posts:delete' // Delete posts
48
+ ],
49
+ permissionLabels: [
50
+ {
51
+ module: 'users',
52
+ zh: '用户管理',
53
+ en: 'User Management'
54
+ },
55
+ {
56
+ module: 'posts',
57
+ zh: '文章管理',
58
+ en: 'Posts Management'
59
+ }
60
+ ]
61
+ }
62
+
63
+ // Initialize Sumor OAuth
64
+ await setupSumor(app, permissions)
65
+
66
+ // Now your app has:
67
+ // - OAuth routes: /api/oauth/authorize, /api/oauth/callback, etc.
68
+ // - JWT middleware: Automatically validates tokens on protected routes
69
+ // - req.sumor: OAuth service available in all request handlers
70
+ // - req.jwtUser: User info from JWT token
71
+
72
+ app.listen(3000, () => console.log('Server ready'))
73
+ ```
74
+
75
+ ### Accessing User Info in Routes
76
+
77
+ ```typescript
78
+ // User info from JWT token is available in req.jwtUser
79
+ app.get('/api/user/profile', (req: any, res) => {
80
+ const user = req.jwtUser
81
+
82
+ res.json({
83
+ userId: user.userId,
84
+ roles: user.roles?.split(','),
85
+ permissions: user.permissions?.split(','),
86
+ verified: user.isVerified
87
+ })
88
+ })
89
+
90
+ // Call OAuth service methods via req.sumor
91
+ app.get('/api/users/:userId', async (req: any, res) => {
92
+ try {
93
+ // Fetch user info from OAuth provider
94
+ const userInfo = await req.sumor.getUserInfo(req.params.userId)
95
+ res.json(userInfo)
96
+ } catch (error) {
97
+ res.status(400).json({ error: error.message })
98
+ }
99
+ })
100
+ ```
101
+
102
+ ## 📚 API Reference
103
+
104
+ ### setupSumor(app, permissions)
105
+
106
+ Initialize Sumor OAuth with your Express application.
107
+
108
+ **Parameters:**
109
+
110
+ - `app` (Express.Application) - Your Express app instance
111
+ - `permissions` (Object) - Permission configuration:
112
+ - `permissions` (string[]) - List of available permissions
113
+ - `permissionLabels` (Array) - Human-readable labels for permission modules
114
+
115
+ **Returns:** Promise<void>
116
+
117
+ **Example:**
118
+
119
+ ```typescript
120
+ const permissions = {
121
+ permissions: ['posts:view', 'posts:edit', 'posts:delete', 'users:view', 'users:create'],
122
+ permissionLabels: [
123
+ {
124
+ module: 'posts',
125
+ zh: '帖子管理',
126
+ en: 'Posts Management'
127
+ }
128
+ ]
129
+ }
130
+ await setupSumor(app, permissions)
131
+ ```
132
+
133
+ ### req.jwtUser
134
+
135
+ Available in all route handlers after middleware initialization. Contains the user info from JWT token.
136
+
137
+ **Properties:**
138
+
139
+ - `userId` (string) - Unique user identifier
140
+ - `roles` (string) - Comma-separated role IDs
141
+ - `permissions` (string) - Comma-separated user permissions
142
+ - `isVerified` (number) - Verification status
143
+ - `tenantId` (string) - Multi-tenant identifier
144
+ - `exp` (number) - Token expiration timestamp
145
+ - `iat` (number) - Token issued at timestamp
146
+
147
+ **Example:**
148
+
149
+ ```typescript
150
+ app.get('/api/protected', (req: any, res) => {
151
+ const { userId, roles, permissions } = req.jwtUser
152
+ res.json({ userId, roles: roles?.split(','), permissions: permissions?.split(',') })
153
+ })
154
+ ```
155
+
156
+ ### req.sumor (OAuthService)
157
+
158
+ Available in all route handlers. Provides methods to call the OAuth provider's API.
159
+
160
+ **Methods:**
161
+
162
+ #### getUserInfo(userId)
163
+
164
+ Get detailed user information from the OAuth provider.
165
+
166
+ ```typescript
167
+ const userInfo = await req.sumor.getUserInfo('user123')
168
+ // Returns: { userId, name, email, avatar, ... }
169
+ ```
170
+
171
+ #### getUsersInfo(userIds)
172
+
173
+ Get information for multiple users.
174
+
175
+ ```typescript
176
+ const users = await req.sumor.getUsersInfo(['user1', 'user2'])
177
+ // Returns: [{ userId, name, ... }, ...]
178
+ ```
179
+
180
+ #### searchUsers(searchTerm, limit)
181
+
182
+ Search for users by name or email.
183
+
184
+ ```typescript
185
+ const results = await req.sumor.searchUsers('john', 20)
186
+ // Returns: [{ userId, name, email, ... }, ...]
187
+ ```
188
+
189
+ #### exchangeCode(grantType, code, redirectUri, codeVerifier)
190
+
191
+ Exchange authorization code for tokens (internal use).
192
+
193
+ ```typescript
194
+ const tokens = await req.sumor.exchangeCode(
195
+ 'authorization_code',
196
+ authCode,
197
+ 'http://localhost:3000/callback',
198
+ codeVerifier
199
+ )
200
+ // Returns: { accessToken, refreshToken, expiresIn, tokenType }
201
+ ```
202
+
203
+ #### checkBlacklist(sessionId)
204
+
205
+ Check if a session token is revoked.
206
+
207
+ ```typescript
208
+ const isBlacklisted = await req.sumor.checkBlacklist(sessionId)
209
+ ```
210
+
211
+ #### revokeSession(sessionId)
212
+
213
+ Revoke (logout) a session.
214
+
215
+ ```typescript
216
+ await req.sumor.revokeSession(sessionId)
217
+ ```
218
+
219
+ ### OAuth Routes
220
+
221
+ Sumor automatically registers these routes:
222
+
223
+ - `GET /api/oauth/info` - Get OAuth provider info and authorization URL (no auth required)
224
+ - `GET /api/oauth/callback` - Handle OAuth provider callback with authorization code (no auth required)
225
+ - `PUT /api/oauth/token` - Refresh access token (can use refreshToken from body or cookie)
226
+ - `POST /api/oauth/logout` - Logout and revoke session (requires valid token)
227
+
228
+ ## 🔧 Configuration
229
+
230
+ Sumor uses environment variables for configuration. The key is configuring the OAuth provider endpoint:
231
+
232
+ ```bash
233
+ # OAuth Provider Configuration
234
+ OAUTH_ENDPOINT=https://auth.example.com
235
+ OAUTH_CLIENT_KEY=your-app-client-id
236
+ OAUTH_CLIENT_SECRET=your-app-client-secret
237
+ OAUTH_REDIRECT_URI=http://localhost:3000/api/oauth/callback
238
+ ```
239
+
240
+ **How it works:**
241
+
242
+ - `OAUTH_ENDPOINT` - Base URL of your OAuth provider (e.g., `https://auth.example.com`)
243
+ - OAuth endpoints are automatically derived: `{OAUTH_ENDPOINT}/api/oauth/...`
244
+ - `OAUTH_CLIENT_KEY` and `OAUTH_CLIENT_SECRET` - OAuth application credentials
245
+ - `OAUTH_REDIRECT_URI` - Callback URL that matches your OAuth provider configuration
246
+ - JWT tokens are verified using JWKS public keys from the OAuth provider (no local secret needed)
247
+
248
+ **Example configurations:**
249
+
250
+ ```bash
251
+ # Local development
252
+ OAUTH_ENDPOINT=http://localhost:3001
253
+ OAUTH_CLIENT_KEY=myapp-dev
254
+ OAUTH_CLIENT_SECRET=myapp-dev-secret
255
+ OAUTH_REDIRECT_URI=http://localhost:3000/api/oauth/callback
256
+
257
+ # Production
258
+ OAUTH_ENDPOINT=https://auth.mycompany.com
259
+ OAUTH_CLIENT_KEY=myapp-prod
260
+ OAUTH_CLIENT_SECRET=<secure-secret>
261
+ OAUTH_REDIRECT_URI=https://app.mycompany.com/api/oauth/callback
262
+ ```
263
+
264
+ ## 📚 Usage Examples
265
+
266
+ ### Example 1: Protect Routes with Permission Checks
267
+
268
+ ```typescript
269
+ app.post('/api/posts', (req: any, res) => {
270
+ // Check if user has permission
271
+ const permissions = req.jwtUser.permissions?.split(',') || []
272
+
273
+ if (!permissions.includes('posts:create')) {
274
+ return res.status(403).json({ error: 'Insufficient permissions' })
275
+ }
276
+
277
+ // Create post logic here
278
+ res.json({ postId: 123 })
279
+ })
280
+ ```
281
+
282
+ ### Example 2: Multi-Tenant Support
283
+
284
+ ```typescript
285
+ app.get('/api/tenant/users', (req: any, res) => {
286
+ const tenantId = req.jwtUser.tenantId
287
+
288
+ // Fetch users for the user's tenant
289
+ db.query('SELECT * FROM users WHERE tenant_id = ?', [tenantId]).then(users => res.json(users))
290
+ })
291
+ ```
292
+
293
+ ### Example 3: Fetch Related User Data
294
+
295
+ ```typescript
296
+ app.get('/api/followers', async (req: any, res) => {
297
+ try {
298
+ // Get list of follower IDs from your local database
299
+ const followerIds = await db.query(
300
+ 'SELECT follower_id FROM relationships WHERE leader_id = ?',
301
+ [req.jwtUser.userId]
302
+ )
303
+
304
+ // Get detailed info for all followers from OAuth provider
305
+ const followerInfo = await req.sumor.getUsersInfo(followerIds.map(r => r.follower_id))
306
+
307
+ res.json(followerInfo)
308
+ } catch (error) {
309
+ res.status(500).json({ error: error.message })
310
+ }
311
+ })
312
+ ```
313
+
314
+ ### Example 4: User Search with Pagination
315
+
316
+ ```typescript
317
+ app.get('/api/search/users', async (req: any, res) => {
318
+ const { q, limit = 20 } = req.query
319
+
320
+ if (!q) {
321
+ return res.status(400).json({ error: 'Search term required' })
322
+ }
323
+
324
+ try {
325
+ const results = await req.sumor.searchUsers(q, limit)
326
+ res.json(results)
327
+ } catch (error) {
328
+ res.status(500).json({ error: error.message })
329
+ }
330
+ })
331
+ ```
332
+
333
+ ## 🛡️ Security Considerations
334
+
335
+ ### Token Storage
336
+
337
+ - Tokens are stored in HTTP-only cookies by default (secure against XSS)
338
+ - Always use HTTPS in production to prevent token interception
339
+ - Refresh tokens should be rotated regularly
340
+
341
+ ### Permission Validation
342
+
343
+ Always validate permissions on sensitive operations:
344
+
345
+ ```typescript
346
+ const userPermissions = req.jwtUser.permissions?.split(',') || []
347
+
348
+ if (!userPermissions.includes('users:edit')) {
349
+ return res.status(403).json({ error: 'User edit permission required' })
350
+ }
351
+ ```
352
+
353
+ ### Session Revocation
354
+
355
+ Logout invalidates tokens immediately:
356
+
357
+ ```typescript
358
+ // On logout
359
+ await req.sumor.revokeSession(req.jwtUser.jti)
360
+ // Token is added to blacklist and becomes invalid
361
+ ```
362
+
363
+ ### CSRF Protection
364
+
365
+ Implement CSRF tokens for state-changing operations:
366
+
367
+ ```typescript
368
+ const csrf = require('csurf')
369
+ const csrfProtection = csrf({ cookie: false })
370
+
371
+ app.post('/api/posts', csrfProtection, (req: any, res) => {
372
+ // Handle POST with CSRF protection
373
+ })
374
+ ```
375
+
376
+ ## 🐛 Troubleshooting
377
+
378
+ ### "Token verification failed"
379
+
380
+ **Cause:** JWT signature doesn't match JWKS public key
381
+
382
+ **Solution:** Ensure `OAUTH_ENDPOINT` is correctly configured. Sumor automatically fetches JWKS from `{OAUTH_ENDPOINT}/api/oauth/jwks`
383
+
384
+ ### "Unauthorized" on protected routes
385
+
386
+ **Cause:** Missing or invalid JWT token in request
387
+
388
+ **Solution:** Client must include Authorization header:
389
+
390
+ ```
391
+ Authorization: Bearer <JWT_TOKEN>
392
+ ```
393
+
394
+ ### "Session not found" when revoking
395
+
396
+ **Cause:** Session ID (jti) is invalid or already revoked
397
+
398
+ **Solution:** Check that `req.jwtUser.jti` contains a valid session ID
399
+
400
+ ### Permission check always fails
401
+
402
+ **Cause:** Permissions string format is incorrect
403
+
404
+ **Solution:** Permissions are comma-separated strings. Parse correctly:
405
+
406
+ ```typescript
407
+ const permissions = req.jwtUser.permissions?.split(',').map(p => p.trim()) || []
408
+ ```
409
+
410
+ ## 🔄 Architecture
411
+
412
+ ```
413
+ ┌──────────────────────────────────────┐
414
+ │ Browser / Mobile Client │
415
+ └───────────────┬──────────────────────┘
416
+ │ (1) Click Login
417
+
418
+ ┌──────────────────────────────────────┐
419
+ │ Your Express App (Port 3000) │
420
+ │ ┌────────────────────────────────┐ │
421
+ │ │ GET /api/oauth/info │ │ (2) Get OAuth URL
422
+ │ │ GET /api/oauth/callback │ │
423
+ │ │ POST /api/oauth/logout │ │
424
+ │ └────────────────────────────────┘ │
425
+ └───────────┬──────────────────────────┘
426
+ │ (3) Redirect to OAuth
427
+
428
+ ┌──────────────────────────────────────┐
429
+ │ OAuth Provider │
430
+ │ {OAUTH_ENDPOINT}/api/oauth/... │
431
+ │ - Issue JWT tokens │
432
+ │ - Manage users & permissions │
433
+ │ - Provide JWKS public keys │
434
+ └──────────────────────────────────────┘
435
+
436
+ │ (4) Verify JWT
437
+
438
+ req.sumor
439
+ ```
440
+
441
+ **Flow Steps:**
442
+
443
+ 1. User clicks "Login" on your app
444
+ 2. App calls `GET /api/oauth/info` to get OAuth provider URL
445
+ 3. User redirected to OAuth provider
446
+ 4. OAuth provider authenticates and redirects back to `/api/oauth/callback` with authorization code
447
+ 5. Server exchanges code for JWT token via ITS OAuth API
448
+ 6. Server verifies JWT signature using ITS JWKS public keys
449
+ 7. Subsequent requests include JWT in Authorization header
450
+ 8. Middleware validates JWT and extracts user info (userId, roles, permissions)
451
+ 9. Routes access user info via `req.jwtUser` and call OAuth service via `req.sumor`
452
+
453
+ ## 🤝 Contributing
454
+
455
+ Contributions are welcome! Please feel free to submit issues and pull requests.
456
+
457
+ ## � License
458
+
459
+ MIT License - see [LICENSE](LICENSE) for details
460
+
461
+ ## 📞 Support
462
+
463
+ For issues, questions, or suggestions, please open an issue on GitHub.
464
+
465
+ ---
466
+
467
+ Made with ❤️ by [Lycoo](https://lycoo.com)
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 获取授权 URL 控制器
3
+ * GET /api/oauth/authorize
4
+ *
5
+ * 返回跳转到 ITS 的授权 URL,前端将用此 URL 进行重定向
6
+ */
7
+ import { Request, Response } from 'express';
8
+ export default function getAuthorizeUrlController(req: Request, res: Response): void;
9
+ //# sourceMappingURL=getAuthorizeUrlController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAuthorizeUrlController.d.ts","sourceRoot":"","sources":["../../../server/controllers/getAuthorizeUrlController.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AA+D3C,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,QAgB5E"}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ /**
3
+ * 获取授权 URL 控制器
4
+ * GET /api/oauth/authorize
5
+ *
6
+ * 返回跳转到 ITS 的授权 URL,前端将用此 URL 进行重定向
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.default = getAuthorizeUrlController;
13
+ const crypto_1 = __importDefault(require("crypto"));
14
+ const config_1 = __importDefault(require("../utils/config"));
15
+ /**
16
+ * 生成 PKCE code_challenge 和 code_verifier
17
+ */
18
+ function generateCodeChallenge() {
19
+ const codeVerifier = crypto_1.default.randomBytes(32).toString('hex');
20
+ const challenge = crypto_1.default
21
+ .createHash('sha256')
22
+ .update(codeVerifier)
23
+ .digest('base64')
24
+ .replace(/\+/g, '-')
25
+ .replace(/\//g, '_')
26
+ .replace(/=/g, '');
27
+ return {
28
+ codeChallenge: challenge,
29
+ codeVerifier: codeVerifier
30
+ };
31
+ }
32
+ /**
33
+ * 生成随机 state 参数
34
+ */
35
+ function generateState() {
36
+ return crypto_1.default.randomBytes(32).toString('hex');
37
+ }
38
+ /**
39
+ * 生成授权 URL
40
+ * RFC 6749 §4.1.1,支持 PKCE(RFC 7636)用于增强安全性
41
+ */
42
+ function generateAuthorizationUrl() {
43
+ const { codeChallenge, codeVerifier } = generateCodeChallenge();
44
+ const uri = config_1.default.redirectUri;
45
+ if (!uri || uri.trim() === '') {
46
+ throw new Error('redirectUri 为空或未配置,无法生成授权 URL');
47
+ }
48
+ const randomState = generateState();
49
+ const encryptedState = Buffer.from(codeVerifier).toString('base64') + ':' + randomState;
50
+ const params = new URLSearchParams({
51
+ clientKey: config_1.default.clientKey,
52
+ redirectUri: uri,
53
+ responseType: 'code',
54
+ scope: 'profile',
55
+ state: encryptedState,
56
+ codeChallenge: codeChallenge,
57
+ codeChallengeMethod: 'S256'
58
+ });
59
+ const authUrl = `${config_1.default.baseUrl}/authorize?${params.toString()}`;
60
+ return authUrl;
61
+ }
62
+ function getAuthorizeUrlController(req, res) {
63
+ try {
64
+ const authUrl = generateAuthorizationUrl();
65
+ res.json({
66
+ code: 'OK',
67
+ data: {
68
+ authUrl
69
+ }
70
+ });
71
+ }
72
+ catch (error) {
73
+ res.status(500).json({
74
+ code: 'ERROR',
75
+ message: error.message
76
+ });
77
+ }
78
+ }
79
+ //# sourceMappingURL=getAuthorizeUrlController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAuthorizeUrlController.js","sourceRoot":"","sources":["../../../server/controllers/getAuthorizeUrlController.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;AAkEH,4CAgBC;AAhFD,oDAA2B;AAE3B,6DAAyC;AAEzC;;GAEG;AACH,SAAS,qBAAqB;IAI5B,MAAM,YAAY,GAAG,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3D,MAAM,SAAS,GAAG,gBAAM;SACrB,UAAU,CAAC,QAAQ,CAAC;SACpB,MAAM,CAAC,YAAY,CAAC;SACpB,MAAM,CAAC,QAAQ,CAAC;SAChB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAEpB,OAAO;QACL,aAAa,EAAE,SAAS;QACxB,YAAY,EAAE,YAAY;KAC3B,CAAA;AACH,CAAC;AAED;;GAEG;AACH,SAAS,aAAa;IACpB,OAAO,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC;AAED;;;GAGG;AACH,SAAS,wBAAwB;IAC/B,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,qBAAqB,EAAE,CAAA;IAE/D,MAAM,GAAG,GAAG,gBAAW,CAAC,WAAW,CAAA;IAEnC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,WAAW,GAAG,aAAa,EAAE,CAAA;IACnC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,GAAG,WAAW,CAAA;IAEvF,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,SAAS,EAAE,gBAAW,CAAC,SAAS;QAChC,WAAW,EAAE,GAAG;QAChB,YAAY,EAAE,MAAM;QACpB,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,cAAc;QACrB,aAAa,EAAE,aAAa;QAC5B,mBAAmB,EAAE,MAAM;KAC5B,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,GAAG,gBAAW,CAAC,OAAO,cAAc,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAA;IAEvE,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAwB,yBAAyB,CAAC,GAAY,EAAE,GAAa;IAC3E,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,wBAAwB,EAAE,CAAA;QAE1C,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,IAAI;YACV,IAAI,EAAE;gBACJ,OAAO;aACR;SACF,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CAAA;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * OAuth 信息控制器
3
+ * 获取 ITS 端点信息
4
+ */
5
+ /**
6
+ * 获取 ITS 配置信息
7
+ * GET /api/oauth/info
8
+ *
9
+ * 无需认证 - 在 JWT 中间件之前调用
10
+ */
11
+ export declare const getItsInfo: (req: any, res: any, next: any) => Promise<void>;
12
+ //# sourceMappingURL=infoController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infoController.d.ts","sourceRoot":"","sources":["../../../server/controllers/infoController.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAiEH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAU,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,MAAM,GAAG,kBAc7D,CAAA"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * OAuth 信息控制器
4
+ * 获取 ITS 端点信息
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.getItsInfo = void 0;
11
+ const crypto_1 = __importDefault(require("crypto"));
12
+ const config_1 = __importDefault(require("../utils/config"));
13
+ /**
14
+ * 生成 PKCE code_challenge 和 code_verifier
15
+ */
16
+ function generateCodeChallenge() {
17
+ const codeVerifier = crypto_1.default.randomBytes(32).toString('hex');
18
+ const challenge = crypto_1.default
19
+ .createHash('sha256')
20
+ .update(codeVerifier)
21
+ .digest('base64')
22
+ .replace(/\+/g, '-')
23
+ .replace(/\//g, '_')
24
+ .replace(/=/g, '');
25
+ return {
26
+ codeChallenge: challenge,
27
+ codeVerifier: codeVerifier
28
+ };
29
+ }
30
+ /**
31
+ * 生成随机 state 参数
32
+ */
33
+ function generateState() {
34
+ return crypto_1.default.randomBytes(32).toString('hex');
35
+ }
36
+ /**
37
+ * 生成授权 URL
38
+ * RFC 6749 §4.1.1,支持 PKCE(RFC 7636)用于增强安全性
39
+ */
40
+ function generateAuthorizationUrl() {
41
+ const { codeChallenge, codeVerifier } = generateCodeChallenge();
42
+ const uri = config_1.default.redirectUri;
43
+ if (!uri || uri.trim() === '') {
44
+ throw new Error('redirectUri 为空或未配置,无法生成授权 URL');
45
+ }
46
+ const randomState = generateState();
47
+ const encryptedState = Buffer.from(codeVerifier).toString('base64') + ':' + randomState;
48
+ const params = new URLSearchParams({
49
+ clientKey: config_1.default.clientKey,
50
+ redirectUri: uri,
51
+ responseType: 'code',
52
+ scope: 'profile',
53
+ state: encryptedState,
54
+ codeChallenge: codeChallenge,
55
+ codeChallengeMethod: 'S256'
56
+ });
57
+ const authUrl = `${config_1.default.baseUrl}/authorize?${params.toString()}`;
58
+ return authUrl;
59
+ }
60
+ /**
61
+ * 获取 ITS 配置信息
62
+ * GET /api/oauth/info
63
+ *
64
+ * 无需认证 - 在 JWT 中间件之前调用
65
+ */
66
+ const getItsInfo = async (req, res, next) => {
67
+ try {
68
+ const oauthAuthorizeUrl = generateAuthorizationUrl();
69
+ res.json({
70
+ code: 'OK',
71
+ data: {
72
+ endpoint: req.config.its?.endpoint,
73
+ authorizeUrl: oauthAuthorizeUrl
74
+ }
75
+ });
76
+ }
77
+ catch (error) {
78
+ next(error);
79
+ }
80
+ };
81
+ exports.getItsInfo = getItsInfo;
82
+ //# sourceMappingURL=infoController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infoController.js","sourceRoot":"","sources":["../../../server/controllers/infoController.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;AAEH,oDAA2B;AAC3B,6DAAyC;AAEzC;;GAEG;AACH,SAAS,qBAAqB;IAI5B,MAAM,YAAY,GAAG,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3D,MAAM,SAAS,GAAG,gBAAM;SACrB,UAAU,CAAC,QAAQ,CAAC;SACpB,MAAM,CAAC,YAAY,CAAC;SACpB,MAAM,CAAC,QAAQ,CAAC;SAChB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAEpB,OAAO;QACL,aAAa,EAAE,SAAS;QACxB,YAAY,EAAE,YAAY;KAC3B,CAAA;AACH,CAAC;AAED;;GAEG;AACH,SAAS,aAAa;IACpB,OAAO,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC;AAED;;;GAGG;AACH,SAAS,wBAAwB;IAC/B,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,qBAAqB,EAAE,CAAA;IAE/D,MAAM,GAAG,GAAG,gBAAW,CAAC,WAAW,CAAA;IAEnC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;IAClD,CAAC;IAED,MAAM,WAAW,GAAG,aAAa,EAAE,CAAA;IACnC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,GAAG,WAAW,CAAA;IAEvF,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,SAAS,EAAE,gBAAW,CAAC,SAAS;QAChC,WAAW,EAAE,GAAG;QAChB,YAAY,EAAE,MAAM;QACpB,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,cAAc;QACrB,aAAa,EAAE,aAAa;QAC5B,mBAAmB,EAAE,MAAM;KAC5B,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,GAAG,gBAAW,CAAC,OAAO,cAAc,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAA;IAEvE,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;GAKG;AACI,MAAM,UAAU,GAAG,KAAK,EAAE,GAAQ,EAAE,GAAQ,EAAE,IAAS,EAAE,EAAE;IAChE,IAAI,CAAC;QACH,MAAM,iBAAiB,GAAG,wBAAwB,EAAE,CAAA;QAEpD,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,IAAI;YACV,IAAI,EAAE;gBACJ,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ;gBAClC,YAAY,EAAE,iBAAiB;aAChC;SACF,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,KAAK,CAAC,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AAdY,QAAA,UAAU,cActB"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * OAuth 用户登出控制器
3
+ */
4
+ import { Response } from 'express';
5
+ /**
6
+ * 用户登出
7
+ * POST /api/oauth/logout
8
+ *
9
+ * 撤销 OAuth Token(可选)并清理会话
10
+ */
11
+ export declare function logout(req: Record<string, any>, res: Response): Promise<Response<any, Record<string, any>>>;
12
+ //# sourceMappingURL=logoutController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logoutController.d.ts","sourceRoot":"","sources":["../../../server/controllers/logoutController.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAIlC;;;;;GAKG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,QAAQ,+CAsDnE"}