@canmingir/link-express 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/routes/oauth.js +136 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canmingir/link-express",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "author": "NucTeam",
@@ -8,7 +8,7 @@ const Permission = require("../models/Permission");
8
8
  const { project } = config();
9
9
 
10
10
  router.post("/", async (req, res) => {
11
- let { appId, projectId, code, refreshToken, redirectUri } = Joi.attempt(
11
+ let { appId, projectId, code, refreshToken, redirectUri, provider } = Joi.attempt(
12
12
  req.body,
13
13
  Joi.object({
14
14
  appId: Joi.string().required(),
@@ -16,69 +16,119 @@ router.post("/", async (req, res) => {
16
16
  code: Joi.string().optional(),
17
17
  refreshToken: Joi.string().optional(),
18
18
  redirectUri: Joi.string().optional(),
19
+ provider: Joi.string().required(),
19
20
  })
20
21
  .required()
21
22
  .options({ stripUnknown: true })
22
23
  );
24
+
23
25
  if (!code && !refreshToken) {
24
26
  return res.status(400).send("Missing OAuth Code and Refresh Token");
25
27
  }
28
+
29
+ const providerConfig = project.oauth.providers[provider];
30
+ if (!providerConfig) {
31
+ return res.status(400).send("Unsupported OAuth provider");
32
+ }
33
+
34
+ let accessTokenForAPI;
35
+ let newRefreshToken = refreshToken;
36
+
26
37
  if (code && redirectUri) {
27
38
  const params = new URLSearchParams();
28
- params.append("client_id", project.oauth.clientId);
29
- params.append("client_secret", process.env.OAUTH_CLIENT_SECRET);
39
+ params.append("grant_type", "authorization_code");
40
+ params.append("client_id", providerConfig.clientId);
41
+ params.append("client_secret", process.env[`${provider.toUpperCase()}_CLIENT_SECRET`]);
30
42
  params.append("code", code);
31
43
  params.append("redirect_uri", redirectUri);
32
- params.append("grant_type", "authorization_code");
33
- const { data } = await axios.post(
34
- project.oauth.tokenUrl,
44
+
45
+ const tokenResponse = await axios.post(
46
+ providerConfig.tokenUrl,
35
47
  params.toString(),
36
48
  {
37
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
49
+ headers: {
50
+ "Content-Type": "application/x-www-form-urlencoded",
51
+ "Accept": "application/json"
52
+ },
53
+ timeout: 10000
38
54
  }
39
55
  );
40
56
 
41
- const urlParams = new URLSearchParams(data);
57
+ if (tokenResponse.data.error) {
58
+ throw new AuthenticationError(
59
+ tokenResponse.data.error_description || tokenResponse.data.error
60
+ );
61
+ }
42
62
 
43
- if (urlParams.get("error")) {
44
- throw new AuthenticationError(urlParams.get("error_description"));
63
+ if (!tokenResponse.data.access_token) {
64
+ throw new AuthenticationError("No access token received from OAuth provider");
45
65
  }
46
66
 
47
- refreshToken = urlParams.get("access_token");
67
+ accessTokenForAPI = tokenResponse.data.access_token;
68
+ newRefreshToken = tokenResponse.data.refresh_token || tokenResponse.data.access_token;
69
+ } else {
70
+ accessTokenForAPI = refreshToken;
48
71
  }
49
72
 
50
- const { data } = await axios.get(project.oauth.userUrl, {
73
+
74
+ let userResponse = await axios.get(providerConfig.userUrl, {
51
75
  headers: {
52
- Authorization: `Bearer ${refreshToken}`,
76
+ Authorization: `Bearer ${accessTokenForAPI}`,
77
+ "Accept": "application/json"
53
78
  },
79
+ timeout: 10000
54
80
  });
55
81
 
56
- const userId = data[project.oauth.jwt.identifier].toString();
82
+ console.log("User info response:", userResponse.data);
83
+
84
+ let userId;
85
+ const identifierField = providerConfig.userIdentifier;
86
+
87
+ if (userResponse.data[identifierField]) {
88
+ userId = userResponse.data[identifierField].toString();
89
+ } else if (userResponse.data[project.oauth.jwt.identifier]) {
90
+ userId = userResponse.data[project.oauth.jwt.identifier].toString();
91
+ } else {
92
+ console.error("User identifier extraction failed:", {
93
+ availableFields: Object.keys(userResponse.data),
94
+ expectedField: identifierField,
95
+ fallbackField: project.oauth.jwt.identifier
96
+ });
97
+ throw new Error(`Cannot find user identifier in ${provider} OAuth response`);
98
+ }
99
+
100
+ const prefixedUserId = `${provider}_${userId}`;
57
101
 
58
102
  let accessToken;
59
103
 
60
104
  if (projectId) {
61
105
  const permissions = await Permission.findAll({
62
- where: { userId, projectId, appId },
106
+ where: { userId: prefixedUserId, projectId, appId },
63
107
  });
64
108
 
65
109
  if (!permissions.length) {
66
110
  accessToken = jwt.sign(
67
- { sub: userId, iss: "nuc", aid: appId },
68
- process.env.JWT_SECRET,
69
111
  {
70
- expiresIn: "12h",
71
- }
112
+ sub: prefixedUserId,
113
+ iss: "nuc",
114
+ aid: appId,
115
+ provider: provider,
116
+ iat: Math.floor(Date.now() / 1000)
117
+ },
118
+ process.env.JWT_SECRET,
119
+ { expiresIn: "12h" }
72
120
  );
73
121
  } else {
74
122
  accessToken = jwt.sign(
75
123
  {
76
- sub: userId,
124
+ sub: prefixedUserId,
77
125
  iss: "nuc",
78
126
  aud: projectId,
79
127
  oid: permissions[0].organizationId,
80
128
  aid: appId,
81
129
  rls: permissions.map((permission) => permission.role),
130
+ provider: provider,
131
+ iat: Math.floor(Date.now() / 1000)
82
132
  },
83
133
  process.env.JWT_SECRET,
84
134
  { expiresIn: "12h" }
@@ -86,15 +136,75 @@ router.post("/", async (req, res) => {
86
136
  }
87
137
  } else {
88
138
  accessToken = jwt.sign(
89
- { sub: userId, iss: "nuc", aid: appId },
90
- process.env.JWT_SECRET,
91
139
  {
92
- expiresIn: "12h",
93
- }
140
+ sub: prefixedUserId,
141
+ iss: "nuc",
142
+ aid: appId,
143
+ provider: provider,
144
+ iat: Math.floor(Date.now() / 1000)
145
+ },
146
+ process.env.JWT_SECRET,
147
+ { expiresIn: "12h" }
94
148
  );
95
149
  }
96
150
 
97
- res.status(200).json({ accessToken, refreshToken });
151
+ res.status(200).json({
152
+ accessToken,
153
+ refreshToken: newRefreshToken
154
+ });
155
+ });
156
+
157
+ router.get("/user", async (req, res) => {
158
+ const authHeader = req.headers.authorization;
159
+ const refreshTokenHeader = req.headers['x-refresh-token'];
160
+
161
+
162
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
163
+ return res.status(401).end();
164
+ }
165
+
166
+ if (!refreshTokenHeader) {
167
+ return res.status(400).send("Missing refresh token");
168
+ }
169
+
170
+ const token = authHeader.split(' ')[1];
171
+
172
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
173
+ const userId = decoded.sub;
174
+ const provider = decoded.provider;
175
+
176
+ if (!userId || !provider) {
177
+ return res.status(401).end();
178
+ }
179
+
180
+ const providerConfig = project.oauth.providers[provider];
181
+ if (!providerConfig) {
182
+ return res.status(400).send("Unsupported OAuth provider");
183
+ }
184
+
185
+
186
+ const userResponse = await axios.get(providerConfig.userUrl, {
187
+ headers: {
188
+ Authorization: `Bearer ${refreshTokenHeader}`,
189
+ "Accept": "application/json"
190
+ },
191
+ timeout: 10000
192
+ });
193
+
194
+
195
+ const userFieldMapping = providerConfig.userFields;
196
+ const userDetails = {
197
+ id: userId,
198
+ provider: provider,
199
+ name: userResponse.data[userFieldMapping.name] || null,
200
+ displayName: userResponse.data[userFieldMapping.displayName] || null,
201
+ avatarUrl: userResponse.data[userFieldMapping.avatarUrl] || null,
202
+ email: userResponse.data[userFieldMapping.email] || null
203
+ };
204
+
205
+ res.status(200).json({
206
+ user: userDetails
207
+ });
98
208
  });
99
209
 
100
- module.exports = router;
210
+ module.exports = router;