sumor 2.0.3 → 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.
- package/LICENSE +1 -1
- package/README.md +467 -1
- package/dist/server/controllers/getAuthorizeUrlController.d.ts +9 -0
- package/dist/server/controllers/getAuthorizeUrlController.d.ts.map +1 -0
- package/dist/server/controllers/getAuthorizeUrlController.js +79 -0
- package/dist/server/controllers/getAuthorizeUrlController.js.map +1 -0
- package/dist/server/controllers/infoController.d.ts +12 -0
- package/dist/server/controllers/infoController.d.ts.map +1 -0
- package/dist/server/controllers/infoController.js +82 -0
- package/dist/server/controllers/infoController.js.map +1 -0
- package/dist/server/controllers/logoutController.d.ts +12 -0
- package/dist/server/controllers/logoutController.d.ts.map +1 -0
- package/dist/server/controllers/logoutController.js +84 -0
- package/dist/server/controllers/logoutController.js.map +1 -0
- package/dist/server/controllers/oauthCallbackController.d.ts +9 -0
- package/dist/server/controllers/oauthCallbackController.d.ts.map +1 -0
- package/dist/server/controllers/oauthCallbackController.js +118 -0
- package/dist/server/controllers/oauthCallbackController.js.map +1 -0
- package/dist/server/controllers/tokenRefreshController.d.ts +9 -0
- package/dist/server/controllers/tokenRefreshController.d.ts.map +1 -0
- package/dist/server/controllers/tokenRefreshController.js +58 -0
- package/dist/server/controllers/tokenRefreshController.js.map +1 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +56 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middlewares/loadJwtUserMiddleware.d.ts +26 -0
- package/dist/server/middlewares/loadJwtUserMiddleware.d.ts.map +1 -0
- package/dist/server/middlewares/loadJwtUserMiddleware.js +64 -0
- package/dist/server/middlewares/loadJwtUserMiddleware.js.map +1 -0
- package/dist/server/models/blacklistModel.d.ts +63 -0
- package/dist/server/models/blacklistModel.d.ts.map +1 -0
- package/dist/server/models/blacklistModel.js +133 -0
- package/dist/server/models/blacklistModel.js.map +1 -0
- package/dist/server/models/jwksModel.d.ts +21 -0
- package/dist/server/models/jwksModel.d.ts.map +1 -0
- package/dist/server/models/jwksModel.js +52 -0
- package/dist/server/models/jwksModel.js.map +1 -0
- package/dist/server/models/tokenModel.d.ts +36 -0
- package/dist/server/models/tokenModel.d.ts.map +1 -0
- package/dist/server/models/tokenModel.js +83 -0
- package/dist/server/models/tokenModel.js.map +1 -0
- package/dist/server/routes.d.ts +7 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/routes.js +51 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/services/oauthService.d.ts +157 -0
- package/dist/server/services/oauthService.d.ts.map +1 -0
- package/dist/server/services/oauthService.js +356 -0
- package/dist/server/services/oauthService.js.map +1 -0
- package/dist/server/types/blacklist.d.ts +11 -0
- package/dist/server/types/blacklist.d.ts.map +1 -0
- package/dist/server/types/blacklist.js +6 -0
- package/dist/server/types/blacklist.js.map +1 -0
- package/dist/server/types/oauth.d.ts +123 -0
- package/dist/server/types/oauth.d.ts.map +1 -0
- package/dist/server/types/oauth.js +6 -0
- package/dist/server/types/oauth.js.map +1 -0
- package/dist/server/utils/authUtils.d.ts +15 -0
- package/dist/server/utils/authUtils.d.ts.map +1 -0
- package/dist/server/utils/authUtils.js +46 -0
- package/dist/server/utils/authUtils.js.map +1 -0
- package/dist/server/utils/config.d.ts +8 -0
- package/dist/server/utils/config.d.ts.map +1 -0
- package/dist/server/utils/config.js +45 -0
- package/dist/server/utils/config.js.map +1 -0
- package/dist/server/utils/http.d.ts +11 -0
- package/dist/server/utils/http.d.ts.map +1 -0
- package/dist/server/utils/http.js +37 -0
- package/dist/server/utils/http.js.map +1 -0
- package/dist/server/utils/oauthTokenUtils.d.ts +10 -0
- package/dist/server/utils/oauthTokenUtils.d.ts.map +1 -0
- package/dist/server/utils/oauthTokenUtils.js +22 -0
- package/dist/server/utils/oauthTokenUtils.js.map +1 -0
- package/dist/web/Sumor.d.ts +61 -0
- package/dist/web/Sumor.d.ts.map +1 -0
- package/dist/web/Sumor.js +207 -0
- package/dist/web/Sumor.js.map +1 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +14 -0
- package/dist/web/index.js.map +1 -0
- package/package.json +65 -66
- package/i18n/README.md +0 -32
- package/i18n/sumor_api.yaml +0 -16
- package/i18n/sumor_app.yaml +0 -3
- package/i18n/sumor_internal.yaml +0 -1
- package/i18n/sumor_token.yaml +0 -2
- package/i18nExt/sumor_api.de.yaml +0 -3
- package/i18nExt/sumor_api.en.yaml +0 -3
- package/i18nExt/sumor_api.es.yaml +0 -3
- package/i18nExt/sumor_api.fr.yaml +0 -3
- package/i18nExt/sumor_api.it.yaml +0 -3
- package/i18nExt/sumor_api.ja.yaml +0 -3
- package/i18nExt/sumor_api.ko.yaml +0 -3
- package/i18nExt/sumor_api.pt.yaml +0 -3
- package/i18nExt/sumor_api.zh-TW.yaml +0 -3
- package/i18nExt/sumor_api.zh.yaml +0 -3
- package/i18nExt/sumor_app.de.yaml +0 -3
- package/i18nExt/sumor_app.en.yaml +0 -3
- package/i18nExt/sumor_app.es.yaml +0 -3
- package/i18nExt/sumor_app.zh-TW.yaml +0 -3
- package/i18nExt/sumor_app.zh.yaml +0 -3
- package/i18nExt/sumor_internal.de.yaml +0 -1
- package/i18nExt/sumor_internal.en.yaml +0 -1
- package/i18nExt/sumor_internal.es.yaml +0 -1
- package/i18nExt/sumor_internal.zh-TW.yaml +0 -1
- package/i18nExt/sumor_internal.zh.yaml +0 -1
- package/i18nExt/sumor_token.de.yaml +0 -2
- package/i18nExt/sumor_token.es.yaml +0 -2
- package/i18nExt/sumor_token.fr.yaml +0 -2
- package/i18nExt/sumor_token.it.yaml +0 -2
- package/i18nExt/sumor_token.ja.yaml +0 -2
- package/i18nExt/sumor_token.ko.yaml +0 -2
- package/i18nExt/sumor_token.pt.yaml +0 -2
- package/i18nExt/sumor_token.ru.yaml +0 -2
- package/i18nExt/sumor_token.tr.yaml +0 -2
- package/i18nExt/sumor_token.zh-TW.yaml +0 -2
- package/i18nExt/sumor_token.zh.yaml +0 -2
- package/modules/alertPage/html/template.html +0 -162
- package/modules/alertPage/html/undraw_access-denied.svg +0 -52
- package/modules/alertPage/html/undraw_alert.svg +0 -1
- package/modules/alertPage/html/undraw_celebration.svg +0 -78
- package/modules/alertPage/html/undraw_complete-form.svg +0 -1
- package/modules/alertPage/html/undraw_login.svg +0 -1
- package/modules/alertPage/index.js +0 -46
- package/modules/alertPage/index.test.js +0 -12
- package/modules/i18n/README.md +0 -90
- package/modules/i18n/convertI18nValue/README.md +0 -31
- package/modules/i18n/convertI18nValue/getI18nTemplate.js +0 -26
- package/modules/i18n/convertI18nValue/getI18nTemplate.test.js +0 -40
- package/modules/i18n/convertI18nValue/index.js +0 -14
- package/modules/i18n/convertI18nValue/index.test.js +0 -55
- package/modules/i18n/convertI18nValue/stringVariableReplace.js +0 -13
- package/modules/i18n/convertI18nValue/stringVariableReplace.test.js +0 -39
- package/modules/i18n/index.js +0 -26
- package/modules/i18n/index.test.js +0 -13
- package/modules/i18n/load/README.md +0 -28
- package/modules/i18n/load/load.js +0 -31
- package/modules/i18n/load/load.test.js +0 -30
- package/modules/i18n/registry.js +0 -48
- package/modules/i18n/registry.test.js +0 -84
- package/modules/logger/convert/parseFile.js +0 -14
- package/modules/logger/convert/parseFile.test.js +0 -28
- package/modules/logger/convert/stringifyCMD.js +0 -48
- package/modules/logger/convert/stringifyCMD.test.js +0 -37
- package/modules/logger/convert/stringifyFile.js +0 -24
- package/modules/logger/convert/stringifyFile.test.js +0 -79
- package/modules/logger/index.js +0 -82
- package/modules/logger/index.test.js +0 -124
- package/modules/logger/logFileOperator.js +0 -50
- package/modules/logger/logFileOperator.test.js +0 -69
- package/modules/middlewares/apiMiddleware/errorCatcher.js +0 -9
- package/modules/middlewares/apiMiddleware/exposeApis/index.js +0 -82
- package/modules/middlewares/apiMiddleware/index.js +0 -111
- package/modules/middlewares/apiMiddleware/index.test.js +0 -145
- package/modules/middlewares/apiMiddleware/load/index.js +0 -35
- package/modules/middlewares/apiMiddleware/load/index.test.js +0 -30
- package/modules/middlewares/apiMiddleware/metadataToSwagger.js +0 -139
- package/modules/middlewares/apiMiddleware/prepareData/format/caseSensitive.js +0 -26
- package/modules/middlewares/apiMiddleware/prepareData/format/caseSensitive.test.js +0 -36
- package/modules/middlewares/apiMiddleware/prepareData/format/convertType.js +0 -48
- package/modules/middlewares/apiMiddleware/prepareData/format/convertType.test.js +0 -53
- package/modules/middlewares/apiMiddleware/prepareData/format/defaultValue.js +0 -31
- package/modules/middlewares/apiMiddleware/prepareData/format/defaultValue.test.js +0 -31
- package/modules/middlewares/apiMiddleware/prepareData/format/index.js +0 -18
- package/modules/middlewares/apiMiddleware/prepareData/format/index.test.js +0 -40
- package/modules/middlewares/apiMiddleware/prepareData/format/precision.js +0 -12
- package/modules/middlewares/apiMiddleware/prepareData/format/precision.test.js +0 -33
- package/modules/middlewares/apiMiddleware/prepareData/format/trim.js +0 -15
- package/modules/middlewares/apiMiddleware/prepareData/format/trim.test.js +0 -24
- package/modules/middlewares/apiMiddleware/prepareData/index.js +0 -29
- package/modules/middlewares/apiMiddleware/prepareData/index.test.js +0 -121
- package/modules/middlewares/apiMiddleware/prepareData/validate/checkLength.js +0 -26
- package/modules/middlewares/apiMiddleware/prepareData/validate/checkLength.test.js +0 -52
- package/modules/middlewares/apiMiddleware/prepareData/validate/index.js +0 -30
- package/modules/middlewares/apiMiddleware/public/favicon.ico +0 -0
- package/modules/middlewares/apiMiddleware/response/sendError.js +0 -57
- package/modules/middlewares/apiMiddleware/response/sendError.test.js +0 -251
- package/modules/middlewares/apiMiddleware/response/sendNotFound.js +0 -26
- package/modules/middlewares/apiMiddleware/response/sendResponse.js +0 -25
- package/modules/middlewares/apiMiddleware/response/sendSuccess.js +0 -30
- package/modules/middlewares/bodyMiddleware/cleanupFiles.js +0 -14
- package/modules/middlewares/bodyMiddleware/cleanupFiles.test.js +0 -54
- package/modules/middlewares/bodyMiddleware/fileParser.js +0 -69
- package/modules/middlewares/bodyMiddleware/fileParser.test.js +0 -163
- package/modules/middlewares/bodyMiddleware/index.js +0 -12
- package/modules/middlewares/bodyMiddleware/index.test.js +0 -64
- package/modules/middlewares/bodyMiddleware/mergeData.js +0 -4
- package/modules/middlewares/bodyMiddleware/mergeData.test.js +0 -38
- package/modules/middlewares/i18nMiddleware/index.js +0 -82
- package/modules/middlewares/i18nMiddleware/index.test.js +0 -75
- package/modules/middlewares/tokenMiddleware/Token.js +0 -115
- package/modules/middlewares/tokenMiddleware/Token.test.js +0 -67
- package/modules/middlewares/tokenMiddleware/index.js +0 -32
- package/modules/middlewares/tokenMiddleware/index.test.js +0 -115
- package/modules/middlewares/tokenMiddleware/parseCookie.js +0 -24
- package/modules/middlewares/tokenMiddleware/parseCookie.test.js +0 -49
- package/modules/serve/formatConfig.js +0 -7
- package/modules/serve/formatConfig.test.js +0 -28
- package/modules/serve/index.js +0 -30
- package/modules/serve/index.test.js +0 -69
- package/modules/serve/listenApp.js +0 -11
- package/modules/system/getSystemLanguage.js +0 -9
- package/modules/system/getSystemLanguage.test.js +0 -19
- package/modules/utils/getError.js +0 -56
- package/modules/utils/getError.test.js +0 -97
- package/modules/utils/loadConfig.js +0 -73
- package/modules/utils/loadConfig.test.js +0 -129
- package/modules/utils/pathUtils.js +0 -43
- package/modules/utils/pathUtils.test.js +0 -52
- package/modules/utils/type.js +0 -14
- package/modules/utils/type.test.js +0 -40
- package/src/app.js +0 -40
- package/src/cli.js +0 -28
- package/src/index.js +0 -3
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1 +1,467 @@
|
|
|
1
|
-
# Sumor
|
|
1
|
+
# Sumor - OAuth Authentication Framework
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/sumor)
|
|
4
|
+
[](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 @@
|
|
|
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"}
|