@canmingir/link-express 1.6.0 → 1.6.2
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/package.json +1 -1
- package/src/lib/test.js +15 -4
- package/src/routes/oauth.js +152 -37
- package/src/routes/test/oauth.spec.js +47 -21
- package/src/routes/test/permissions.spec.js +24 -0
- package/src/seeds/Permission.json +172 -164
package/package.json
CHANGED
package/src/lib/test.js
CHANGED
|
@@ -7,10 +7,21 @@ init({
|
|
|
7
7
|
jwt: {
|
|
8
8
|
identifier: "email",
|
|
9
9
|
},
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
providers: {
|
|
11
|
+
github: {
|
|
12
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
13
|
+
userUrl: "https://api.github.com/user",
|
|
14
|
+
clientId: "0c2844d3d19dc9293fc5",
|
|
15
|
+
redirectUri: "http://localhost:5173/callback",
|
|
16
|
+
userIdentifier: "id",
|
|
17
|
+
userFields: {
|
|
18
|
+
name: "name",
|
|
19
|
+
displayName: "login",
|
|
20
|
+
avatarUrl: "avatar_url",
|
|
21
|
+
email: "email",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
14
25
|
},
|
|
15
26
|
},
|
|
16
27
|
postgres: {
|
package/src/routes/oauth.js
CHANGED
|
@@ -3,82 +3,140 @@ const router = require("express").Router();
|
|
|
3
3
|
const jwt = require("jsonwebtoken");
|
|
4
4
|
const axios = require("axios");
|
|
5
5
|
const config = require("../config");
|
|
6
|
-
const { AuthenticationError } = require("../error");
|
|
6
|
+
const { AuthenticationError, AuthorizationError } = require("../error");
|
|
7
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 } =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
let { appId, projectId, code, refreshToken, redirectUri, provider } =
|
|
12
|
+
Joi.attempt(
|
|
13
|
+
req.body,
|
|
14
|
+
Joi.object({
|
|
15
|
+
appId: Joi.string().required(),
|
|
16
|
+
projectId: Joi.string().optional(),
|
|
17
|
+
code: Joi.string().optional(),
|
|
18
|
+
refreshToken: Joi.string().optional(),
|
|
19
|
+
redirectUri: Joi.string().optional(),
|
|
20
|
+
provider: Joi.string().required(),
|
|
21
|
+
})
|
|
22
|
+
.required()
|
|
23
|
+
.options({ stripUnknown: true })
|
|
24
|
+
);
|
|
25
|
+
|
|
23
26
|
if (!code && !refreshToken) {
|
|
24
27
|
return res.status(400).send("Missing OAuth Code and Refresh Token");
|
|
25
28
|
}
|
|
29
|
+
|
|
30
|
+
const providerConfig = project.oauth.providers[provider];
|
|
31
|
+
if (!providerConfig) {
|
|
32
|
+
return res.status(400).send("Unsupported OAuth provider");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let accessTokenForAPI;
|
|
36
|
+
let newRefreshToken = refreshToken;
|
|
37
|
+
|
|
26
38
|
if (code && redirectUri) {
|
|
27
39
|
const params = new URLSearchParams();
|
|
28
|
-
params.append("
|
|
29
|
-
params.append("
|
|
40
|
+
params.append("grant_type", "authorization_code");
|
|
41
|
+
params.append("client_id", providerConfig.clientId);
|
|
42
|
+
params.append(
|
|
43
|
+
"client_secret",
|
|
44
|
+
process.env[`${provider.toUpperCase()}_CLIENT_SECRET`]
|
|
45
|
+
);
|
|
30
46
|
params.append("code", code);
|
|
31
47
|
params.append("redirect_uri", redirectUri);
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
48
|
+
|
|
49
|
+
const tokenResponse = await axios.post(
|
|
50
|
+
providerConfig.tokenUrl,
|
|
35
51
|
params.toString(),
|
|
36
52
|
{
|
|
37
|
-
headers: {
|
|
53
|
+
headers: {
|
|
54
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
55
|
+
Accept: "application/json",
|
|
56
|
+
},
|
|
57
|
+
timeout: 10000,
|
|
38
58
|
}
|
|
39
59
|
);
|
|
40
60
|
|
|
41
|
-
|
|
61
|
+
if (tokenResponse.data.error) {
|
|
62
|
+
throw new AuthorizationError(
|
|
63
|
+
tokenResponse.data.error_description || tokenResponse.data.error
|
|
64
|
+
);
|
|
65
|
+
}
|
|
42
66
|
|
|
43
|
-
if (
|
|
44
|
-
throw new AuthenticationError(
|
|
67
|
+
if (!tokenResponse.data.access_token) {
|
|
68
|
+
throw new AuthenticationError(
|
|
69
|
+
"No access token received from OAuth provider"
|
|
70
|
+
);
|
|
45
71
|
}
|
|
46
72
|
|
|
47
|
-
|
|
73
|
+
accessTokenForAPI = tokenResponse.data.access_token;
|
|
74
|
+
newRefreshToken =
|
|
75
|
+
tokenResponse.data.refresh_token || tokenResponse.data.access_token;
|
|
76
|
+
} else {
|
|
77
|
+
accessTokenForAPI = refreshToken;
|
|
48
78
|
}
|
|
49
79
|
|
|
50
|
-
|
|
80
|
+
let userResponse = await axios.get(providerConfig.userUrl, {
|
|
51
81
|
headers: {
|
|
52
|
-
Authorization: `Bearer ${
|
|
82
|
+
Authorization: `Bearer ${accessTokenForAPI}`,
|
|
83
|
+
Accept: "application/json",
|
|
53
84
|
},
|
|
85
|
+
timeout: 10000,
|
|
54
86
|
});
|
|
55
87
|
|
|
56
|
-
|
|
88
|
+
console.log("User info response:", userResponse.data);
|
|
89
|
+
|
|
90
|
+
let userId;
|
|
91
|
+
const identifierField = providerConfig.userIdentifier;
|
|
92
|
+
|
|
93
|
+
if (userResponse.data[identifierField]) {
|
|
94
|
+
userId = userResponse.data[identifierField].toString();
|
|
95
|
+
} else if (userResponse.data[project.oauth.jwt.identifier]) {
|
|
96
|
+
userId = userResponse.data[project.oauth.jwt.identifier].toString();
|
|
97
|
+
} else {
|
|
98
|
+
console.error("User identifier extraction failed:", {
|
|
99
|
+
availableFields: Object.keys(userResponse.data),
|
|
100
|
+
expectedField: identifierField,
|
|
101
|
+
fallbackField: project.oauth.jwt.identifier,
|
|
102
|
+
});
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Cannot find user identifier in ${provider} OAuth response`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const prefixedUserId = `${provider}_${userId}`;
|
|
57
109
|
|
|
58
110
|
let accessToken;
|
|
59
111
|
|
|
60
112
|
if (projectId) {
|
|
61
113
|
const permissions = await Permission.findAll({
|
|
62
|
-
where: { userId, projectId, appId },
|
|
114
|
+
where: { userId: prefixedUserId, projectId, appId },
|
|
63
115
|
});
|
|
64
116
|
|
|
65
117
|
if (!permissions.length) {
|
|
66
118
|
accessToken = jwt.sign(
|
|
67
|
-
{ sub: userId, iss: "nuc", aid: appId },
|
|
68
|
-
process.env.JWT_SECRET,
|
|
69
119
|
{
|
|
70
|
-
|
|
71
|
-
|
|
120
|
+
sub: prefixedUserId,
|
|
121
|
+
iss: "nuc",
|
|
122
|
+
aid: appId,
|
|
123
|
+
provider: provider,
|
|
124
|
+
iat: Math.floor(Date.now() / 1000),
|
|
125
|
+
},
|
|
126
|
+
process.env.JWT_SECRET,
|
|
127
|
+
{ expiresIn: "12h" }
|
|
72
128
|
);
|
|
73
129
|
} else {
|
|
74
130
|
accessToken = jwt.sign(
|
|
75
131
|
{
|
|
76
|
-
sub:
|
|
132
|
+
sub: prefixedUserId,
|
|
77
133
|
iss: "nuc",
|
|
78
134
|
aud: projectId,
|
|
79
135
|
oid: permissions[0].organizationId,
|
|
80
136
|
aid: appId,
|
|
81
137
|
rls: permissions.map((permission) => permission.role),
|
|
138
|
+
provider: provider,
|
|
139
|
+
iat: Math.floor(Date.now() / 1000),
|
|
82
140
|
},
|
|
83
141
|
process.env.JWT_SECRET,
|
|
84
142
|
{ expiresIn: "12h" }
|
|
@@ -86,15 +144,72 @@ router.post("/", async (req, res) => {
|
|
|
86
144
|
}
|
|
87
145
|
} else {
|
|
88
146
|
accessToken = jwt.sign(
|
|
89
|
-
{ sub: userId, iss: "nuc", aid: appId },
|
|
90
|
-
process.env.JWT_SECRET,
|
|
91
147
|
{
|
|
92
|
-
|
|
93
|
-
|
|
148
|
+
sub: prefixedUserId,
|
|
149
|
+
iss: "nuc",
|
|
150
|
+
aid: appId,
|
|
151
|
+
provider: provider,
|
|
152
|
+
iat: Math.floor(Date.now() / 1000),
|
|
153
|
+
},
|
|
154
|
+
process.env.JWT_SECRET,
|
|
155
|
+
{ expiresIn: "12h" }
|
|
94
156
|
);
|
|
95
157
|
}
|
|
96
158
|
|
|
97
|
-
res.status(200).json({
|
|
159
|
+
res.status(200).json({
|
|
160
|
+
accessToken,
|
|
161
|
+
refreshToken: newRefreshToken,
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
router.get("/user", async (req, res) => {
|
|
166
|
+
const authHeader = req.headers.authorization;
|
|
167
|
+
const refreshTokenHeader = req.headers["x-refresh-token"];
|
|
168
|
+
|
|
169
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
170
|
+
return res.status(401).end();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!refreshTokenHeader) {
|
|
174
|
+
return res.status(400).send("Missing refresh token");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const token = authHeader.split(" ")[1];
|
|
178
|
+
|
|
179
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
180
|
+
const userId = decoded.sub;
|
|
181
|
+
const provider = decoded.provider;
|
|
182
|
+
|
|
183
|
+
if (!userId || !provider) {
|
|
184
|
+
return res.status(401).end();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const providerConfig = project.oauth.providers[provider];
|
|
188
|
+
if (!providerConfig) {
|
|
189
|
+
return res.status(400).send("Unsupported OAuth provider");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const userResponse = await axios.get(providerConfig.userUrl, {
|
|
193
|
+
headers: {
|
|
194
|
+
Authorization: `Bearer ${refreshTokenHeader}`,
|
|
195
|
+
Accept: "application/json",
|
|
196
|
+
},
|
|
197
|
+
timeout: 10000,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const userFieldMapping = providerConfig.userFields;
|
|
201
|
+
const userDetails = {
|
|
202
|
+
id: userId,
|
|
203
|
+
provider: provider,
|
|
204
|
+
name: userResponse.data[userFieldMapping.name] || null,
|
|
205
|
+
displayName: userResponse.data[userFieldMapping.displayName] || null,
|
|
206
|
+
avatarUrl: userResponse.data[userFieldMapping.avatarUrl] || null,
|
|
207
|
+
email: userResponse.data[userFieldMapping.email] || null,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
res.status(200).json({
|
|
211
|
+
user: userDetails,
|
|
212
|
+
});
|
|
98
213
|
});
|
|
99
214
|
|
|
100
215
|
module.exports = router;
|
|
@@ -5,7 +5,7 @@ const app = platform.express();
|
|
|
5
5
|
|
|
6
6
|
const request = require("supertest");
|
|
7
7
|
const jwt = require("jsonwebtoken");
|
|
8
|
-
const { equal } = require("assert");
|
|
8
|
+
const { equal, deepEqual } = require("assert");
|
|
9
9
|
const config = require("../../config");
|
|
10
10
|
const { project } = config();
|
|
11
11
|
|
|
@@ -19,14 +19,16 @@ describe("OAuth", () => {
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it("returns accessToken and refreshToken with code", async () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const provider = "github";
|
|
23
|
+
const providerConfig = project.oauth.providers[provider];
|
|
24
|
+
|
|
25
|
+
mock.onPost(providerConfig.tokenUrl).reply(200, {
|
|
26
|
+
access_token: "c9Q2KuluvCGdM4YZiUnGWxImvuFnbv",
|
|
27
|
+
token_type: "bearer",
|
|
28
|
+
scope: "user",
|
|
29
|
+
});
|
|
28
30
|
|
|
29
|
-
mock.onGet(
|
|
31
|
+
mock.onGet(providerConfig.userUrl).reply(200, { id: "1001" });
|
|
30
32
|
|
|
31
33
|
const {
|
|
32
34
|
body: { accessToken, refreshToken },
|
|
@@ -35,8 +37,9 @@ describe("OAuth", () => {
|
|
|
35
37
|
.send({
|
|
36
38
|
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
37
39
|
projectId: "cb16e069-6214-47f1-9922-1f7fe7629525",
|
|
38
|
-
redirectUri:
|
|
40
|
+
redirectUri: providerConfig.redirectUri,
|
|
39
41
|
code: "vImIDQtMVcYnUCI3Brp6",
|
|
42
|
+
provider: provider,
|
|
40
43
|
})
|
|
41
44
|
.expect(200);
|
|
42
45
|
|
|
@@ -44,17 +47,21 @@ describe("OAuth", () => {
|
|
|
44
47
|
|
|
45
48
|
equal(payload.iss, "nuc");
|
|
46
49
|
equal(payload.aud, "cb16e069-6214-47f1-9922-1f7fe7629525");
|
|
47
|
-
equal(payload.sub,
|
|
48
|
-
|
|
50
|
+
equal(payload.sub, `${provider}_1001`);
|
|
51
|
+
deepEqual(payload.rls, ["OWNER"]);
|
|
49
52
|
equal(payload.aid, "977f5f57-8936-4388-8eb0-00a512cf01cc");
|
|
50
53
|
equal(payload.oid, "dfb990bb-81dd-4584-82ce-050eb8f6a12f");
|
|
54
|
+
equal(payload.provider, provider);
|
|
51
55
|
equal(refreshToken, "c9Q2KuluvCGdM4YZiUnGWxImvuFnbv");
|
|
52
56
|
});
|
|
53
57
|
|
|
54
58
|
it("returns accessToken and refreshToken with refresh token", async () => {
|
|
59
|
+
const provider = "github";
|
|
60
|
+
const providerConfig = project.oauth.providers[provider];
|
|
61
|
+
|
|
55
62
|
mock
|
|
56
|
-
.onGet(
|
|
57
|
-
.reply(200, {
|
|
63
|
+
.onGet(providerConfig.userUrl)
|
|
64
|
+
.reply(200, { id: "liam@rebellioncoffee.shop" });
|
|
58
65
|
|
|
59
66
|
const {
|
|
60
67
|
body: { accessToken, refreshToken },
|
|
@@ -63,42 +70,59 @@ describe("OAuth", () => {
|
|
|
63
70
|
.send({
|
|
64
71
|
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
65
72
|
refreshToken: "lzk7FZGga5hHrfiAePtswijiJHIOev",
|
|
73
|
+
provider: provider,
|
|
66
74
|
})
|
|
67
75
|
.expect(200);
|
|
68
76
|
|
|
69
77
|
const payload = jwt.decode(accessToken);
|
|
70
78
|
|
|
71
|
-
equal(payload.sub,
|
|
79
|
+
equal(payload.sub, `${provider}_liam@rebellioncoffee.shop`);
|
|
72
80
|
equal(payload.iss, "nuc");
|
|
81
|
+
equal(payload.provider, provider);
|
|
73
82
|
equal(refreshToken, "lzk7FZGga5hHrfiAePtswijiJHIOev");
|
|
74
83
|
});
|
|
75
84
|
|
|
76
85
|
it("returns 400 if code and refreshToken are missing", async () => {
|
|
77
|
-
await request(app)
|
|
86
|
+
await request(app)
|
|
87
|
+
.post("/oauth")
|
|
88
|
+
.send({
|
|
89
|
+
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
90
|
+
provider: "github",
|
|
91
|
+
})
|
|
92
|
+
.expect(400);
|
|
78
93
|
});
|
|
79
94
|
|
|
80
95
|
it("returns 401 if code is invalid", async () => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
mock.
|
|
96
|
+
const provider = "github";
|
|
97
|
+
const providerConfig = project.oauth.providers[provider];
|
|
98
|
+
|
|
99
|
+
mock.onPost(providerConfig.tokenUrl).reply(200, {
|
|
100
|
+
error: "bad_verification_code",
|
|
101
|
+
});
|
|
102
|
+
mock.onGet(providerConfig.userUrl).reply(401);
|
|
85
103
|
|
|
86
104
|
const res = await request(app).post("/oauth").send({
|
|
87
105
|
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
88
106
|
code: "ZpodRqsLu2EJxbVrcqnV",
|
|
107
|
+
redirectUri: providerConfig.redirectUri,
|
|
108
|
+
provider: provider,
|
|
89
109
|
});
|
|
90
110
|
equal(res.status, 401);
|
|
91
111
|
});
|
|
92
112
|
|
|
93
113
|
it("returns 503 if OAuth Provider is not accessible", async () => {
|
|
94
|
-
|
|
95
|
-
|
|
114
|
+
const provider = "github";
|
|
115
|
+
const providerConfig = project.oauth.providers[provider];
|
|
116
|
+
|
|
117
|
+
mock.onPost(providerConfig.tokenUrl).networkError();
|
|
118
|
+
mock.onGet(providerConfig.userUrl).networkError();
|
|
96
119
|
|
|
97
120
|
await request(app)
|
|
98
121
|
.post("/oauth")
|
|
99
122
|
.send({
|
|
100
123
|
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
101
124
|
refreshToken: "WnhGHF55s6HFRgpRL9AcV2N2VcYemj",
|
|
125
|
+
provider: provider,
|
|
102
126
|
})
|
|
103
127
|
.expect(503);
|
|
104
128
|
|
|
@@ -107,6 +131,8 @@ describe("OAuth", () => {
|
|
|
107
131
|
.send({
|
|
108
132
|
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
109
133
|
code: "RwlaK2waOdbAa4tt19RF",
|
|
134
|
+
redirectUri: providerConfig.redirectUri,
|
|
135
|
+
provider: provider,
|
|
110
136
|
})
|
|
111
137
|
.expect(503);
|
|
112
138
|
});
|
|
@@ -38,6 +38,14 @@ describe("Permissions", () => {
|
|
|
38
38
|
userId: "1001",
|
|
39
39
|
role: "OWNER",
|
|
40
40
|
},
|
|
41
|
+
{
|
|
42
|
+
id: "f81887da-d05f-4959-9def-6cd137857099",
|
|
43
|
+
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
44
|
+
organizationId: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
45
|
+
projectId: "cb16e069-6214-47f1-9922-1f7fe7629525",
|
|
46
|
+
userId: "github_1001",
|
|
47
|
+
role: "OWNER",
|
|
48
|
+
},
|
|
41
49
|
{
|
|
42
50
|
id: "a1b60c53-66e2-4034-8654-38b83577f279",
|
|
43
51
|
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
@@ -88,6 +96,14 @@ describe("Permissions", () => {
|
|
|
88
96
|
userId: "1001",
|
|
89
97
|
role: "OWNER",
|
|
90
98
|
},
|
|
99
|
+
{
|
|
100
|
+
id: "f81887da-d05f-4959-9def-6cd137857099",
|
|
101
|
+
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
102
|
+
organizationId: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
103
|
+
projectId: "cb16e069-6214-47f1-9922-1f7fe7629525",
|
|
104
|
+
userId: "github_1001",
|
|
105
|
+
role: "OWNER",
|
|
106
|
+
},
|
|
91
107
|
{
|
|
92
108
|
id: "a1b60c53-66e2-4034-8654-38b83577f279",
|
|
93
109
|
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
@@ -125,6 +141,14 @@ describe("Permissions", () => {
|
|
|
125
141
|
.expect(200);
|
|
126
142
|
|
|
127
143
|
deepEqual(permissions, [
|
|
144
|
+
{
|
|
145
|
+
id: "f81887da-d05f-4959-9def-6cd137857099",
|
|
146
|
+
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
147
|
+
organizationId: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
148
|
+
projectId: "cb16e069-6214-47f1-9922-1f7fe7629525",
|
|
149
|
+
userId: "github_1001",
|
|
150
|
+
role: "OWNER",
|
|
151
|
+
},
|
|
128
152
|
{
|
|
129
153
|
id: "a1b60c53-66e2-4034-8654-38b83577f279",
|
|
130
154
|
appId: "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
@@ -1,165 +1,173 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
2
|
+
"sequence": 9007199254740991,
|
|
3
|
+
"seed": [
|
|
4
|
+
{
|
|
5
|
+
"id": "e81887da-d05f-4959-9def-6cd137857088",
|
|
6
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
7
|
+
"organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
8
|
+
"projectId": "cb16e069-6214-47f1-9922-1f7fe7629525",
|
|
9
|
+
"userId": "1001",
|
|
10
|
+
"role": "OWNER"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "f81887da-d05f-4959-9def-6cd137857099",
|
|
14
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
15
|
+
"organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
16
|
+
"projectId": "cb16e069-6214-47f1-9922-1f7fe7629525",
|
|
17
|
+
"userId": "github_1001",
|
|
18
|
+
"role": "OWNER"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "b8e498d4-2d79-4e6e-b098-6a97e1d89a94",
|
|
22
|
+
"appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
|
|
23
|
+
"organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
24
|
+
"projectId": "add6dfa4-45ba-4da2-bc5c-5a529610b52f",
|
|
25
|
+
"userId": "1001",
|
|
26
|
+
"role": "OWNER"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "30044ea7-aa19-4696-876b-26fa76bb91f3",
|
|
30
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
31
|
+
"organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
|
|
32
|
+
"projectId": "0c756054-2d28-4f87-9b12-8023a79136a5",
|
|
33
|
+
"userId": "1001",
|
|
34
|
+
"role": "OWNER"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "973dfaff-0718-4d2d-a714-587cf7a44b98",
|
|
38
|
+
"appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
|
|
39
|
+
"organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
|
|
40
|
+
"projectId": "e6d4744d-a11b-4c75-acad-e24a02903729",
|
|
41
|
+
"userId": "1001",
|
|
42
|
+
"role": "OWNER"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "4ecc380a-b480-4faa-ad47-878dd2ee4264",
|
|
46
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
47
|
+
"organizationId": "5459ab03-204a-4627-bdde-667b7802cb35",
|
|
48
|
+
"projectId": "21d2530b-4657-4ac0-b8cd-1a9f82786e32",
|
|
49
|
+
"userId": "1001",
|
|
50
|
+
"role": "OWNER"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "a1b60c53-66e2-4034-8654-38b83577f279",
|
|
54
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
55
|
+
"organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
56
|
+
"projectId": "cb16e069-6214-47f1-9922-1f7fe7629525",
|
|
57
|
+
"userId": "49736917",
|
|
58
|
+
"role": "OWNER"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "e2809e32-b025-4592-855f-dd2b8f49e1b2",
|
|
62
|
+
"appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
|
|
63
|
+
"organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
64
|
+
"projectId": "add6dfa4-45ba-4da2-bc5c-5a529610b52f",
|
|
65
|
+
"userId": "49736917",
|
|
66
|
+
"role": "OWNER"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "9fc7dd4e-97ef-47a5-987c-99f8015fc736",
|
|
70
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
71
|
+
"organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
|
|
72
|
+
"projectId": "0c756054-2d28-4f87-9b12-8023a79136a5",
|
|
73
|
+
"userId": "49736917",
|
|
74
|
+
"role": "OWNER"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "b8302e81-4a44-4358-978f-21e9219ead61",
|
|
78
|
+
"appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
|
|
79
|
+
"organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
|
|
80
|
+
"projectId": "e6d4744d-a11b-4c75-acad-e24a02903729",
|
|
81
|
+
"userId": "49736917",
|
|
82
|
+
"role": "OWNER"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "4758a390-4968-44bd-aafe-abdee8044a01",
|
|
86
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
87
|
+
"organizationId": "5459ab03-204a-4627-bdde-667b7802cb35",
|
|
88
|
+
"projectId": "21d2530b-4657-4ac0-b8cd-1a9f82786e32",
|
|
89
|
+
"userId": "49736917",
|
|
90
|
+
"role": "OWNER"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "38b1cc0c-46d8-4f95-9be9-0df1a6c91d7b",
|
|
94
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
95
|
+
"organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
96
|
+
"projectId": "cb16e069-6214-47f1-9922-1f7fe7629525",
|
|
97
|
+
"userId": "54210920",
|
|
98
|
+
"role": "OWNER"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "03a1eb7b-b25a-4b44-9484-f8b93d8a9e5b",
|
|
102
|
+
"appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
|
|
103
|
+
"organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
104
|
+
"projectId": "add6dfa4-45ba-4da2-bc5c-5a529610b52f",
|
|
105
|
+
"userId": "54210920",
|
|
106
|
+
"role": "OWNER"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "e54b340d-e1d7-452d-8422-13cae24b0a9f",
|
|
110
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
111
|
+
"organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
|
|
112
|
+
"projectId": "0c756054-2d28-4f87-9b12-8023a79136a5",
|
|
113
|
+
"userId": "54210920",
|
|
114
|
+
"role": "OWNER"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "fce2b3bb-2776-43d8-bb35-14633882b899",
|
|
118
|
+
"appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
|
|
119
|
+
"organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
|
|
120
|
+
"projectId": "e6d4744d-a11b-4c75-acad-e24a02903729",
|
|
121
|
+
"userId": "54210920",
|
|
122
|
+
"role": "OWNER"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "9067f420-9c22-4578-81b6-aa0d88213f24",
|
|
126
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
127
|
+
"organizationId": "5459ab03-204a-4627-bdde-667b7802cb35",
|
|
128
|
+
"projectId": "21d2530b-4657-4ac0-b8cd-1a9f82786e32",
|
|
129
|
+
"userId": "54210920",
|
|
130
|
+
"role": "OWNER"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "7f7bd2c8-3c27-455c-814e-1874e96246ed",
|
|
134
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
135
|
+
"organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
136
|
+
"projectId": "cb16e069-6214-47f1-9922-1f7fe7629525",
|
|
137
|
+
"userId": "90180086",
|
|
138
|
+
"role": "OWNER"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"id": "f09e5539-a983-424c-943f-f9c0e5cffa4e",
|
|
142
|
+
"appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
|
|
143
|
+
"organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
|
|
144
|
+
"projectId": "add6dfa4-45ba-4da2-bc5c-5a529610b52f",
|
|
145
|
+
"userId": "90180086",
|
|
146
|
+
"role": "OWNER"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"id": "bd4d6afe-4592-4739-84af-011760879b3d",
|
|
150
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
151
|
+
"organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
|
|
152
|
+
"projectId": "0c756054-2d28-4f87-9b12-8023a79136a5",
|
|
153
|
+
"userId": "90180086",
|
|
154
|
+
"role": "OWNER"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"id": "5c76772d-40af-464d-ae35-4415bbddc3cf",
|
|
158
|
+
"appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
|
|
159
|
+
"organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
|
|
160
|
+
"projectId": "e6d4744d-a11b-4c75-acad-e24a02903729",
|
|
161
|
+
"userId": "90180086",
|
|
162
|
+
"role": "OWNER"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"id": "20d0920f-00bb-404f-a01e-1ab40eaa8583",
|
|
166
|
+
"appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
|
|
167
|
+
"organizationId": "5459ab03-204a-4627-bdde-667b7802cb35",
|
|
168
|
+
"projectId": "21d2530b-4657-4ac0-b8cd-1a9f82786e32",
|
|
169
|
+
"userId": "90180086",
|
|
170
|
+
"role": "OWNER"
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|