@arcote.tech/arc-auth 0.4.11 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/aggregates/account.ts +207 -218
- package/src/aggregates/oauth-identity.ts +58 -54
- package/src/auth-builder.ts +10 -7
- package/src/routes/oauth-routes.ts +5 -1
- package/src/tokens/token.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-auth",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Reusable authentication module for Arc framework — aggregate-based auth with factory pattern",
|
|
7
7
|
"main": "./src/index.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type-check": "tsc --noEmit"
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
13
|
-
"@arcote.tech/arc": "^0.
|
|
13
|
+
"@arcote.tech/arc": "^0.5.0",
|
|
14
14
|
"react": "^18.0.0 || ^19.0.0",
|
|
15
15
|
"typescript": "^5.0.0"
|
|
16
16
|
},
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
aggregate,
|
|
4
4
|
boolean,
|
|
5
5
|
date,
|
|
6
|
+
mergeUnsafe,
|
|
6
7
|
string,
|
|
7
|
-
type $type,
|
|
8
8
|
type ArcRawShape,
|
|
9
9
|
} from "@arcote.tech/arc";
|
|
10
10
|
import type { AccountId } from "../ids/account";
|
|
@@ -41,11 +41,17 @@ export const createAccountAggregate = <
|
|
|
41
41
|
const { accountId, token, customFields } = data;
|
|
42
42
|
const tokenFields = data.tokenFields ?? [];
|
|
43
43
|
|
|
44
|
-
const name = data.name as Data[
|
|
44
|
+
const name = data.name as Data["name"];
|
|
45
45
|
|
|
46
46
|
/** Build JWT params from account — includes accountId + tokenFields */
|
|
47
|
-
const buildTokenParams = (account:
|
|
48
|
-
|
|
47
|
+
const buildTokenParams = (account: {
|
|
48
|
+
_id: unknown;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}) => {
|
|
51
|
+
const params = { accountId: account._id as string } as Record<
|
|
52
|
+
string,
|
|
53
|
+
unknown
|
|
54
|
+
> & { accountId: string };
|
|
49
55
|
for (const field of tokenFields) {
|
|
50
56
|
params[field] = account[field];
|
|
51
57
|
}
|
|
@@ -53,263 +59,246 @@ export const createAccountAggregate = <
|
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
return (
|
|
56
|
-
aggregate(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
authMethod: string(),
|
|
61
|
-
registeredAt: date(),
|
|
62
|
-
lastSignedInAt: date().optional(),
|
|
63
|
-
...customFields,
|
|
64
|
-
})
|
|
65
|
-
// --- Public Events ---
|
|
66
|
-
|
|
67
|
-
.publicEvent(
|
|
68
|
-
`${name}AccountRegistered`,
|
|
62
|
+
aggregate(
|
|
63
|
+
`${name}Accounts`,
|
|
64
|
+
accountId,
|
|
65
|
+
mergeUnsafe(
|
|
69
66
|
{
|
|
70
|
-
accountId,
|
|
71
67
|
email: string().email(),
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
isEmailVerified: boolean(),
|
|
69
|
+
passwordHash: string().optional(),
|
|
70
|
+
authMethod: string(),
|
|
71
|
+
registeredAt: date(),
|
|
72
|
+
lastSignedInAt: date().optional(),
|
|
74
73
|
},
|
|
74
|
+
customFields,
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
// --- Public Events ---
|
|
78
|
+
|
|
79
|
+
.publicEvent(
|
|
80
|
+
"accountRegistered",
|
|
81
|
+
mergeUnsafe(
|
|
82
|
+
{
|
|
83
|
+
accountId,
|
|
84
|
+
email: string().email(),
|
|
85
|
+
passwordHash: string(),
|
|
86
|
+
},
|
|
87
|
+
customFields,
|
|
88
|
+
),
|
|
75
89
|
async (ctx, event) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
email,
|
|
79
|
-
passwordHash,
|
|
80
|
-
...customData
|
|
81
|
-
} = event.payload as any;
|
|
82
|
-
await ctx.set(id, {
|
|
83
|
-
email,
|
|
90
|
+
event.payload;
|
|
91
|
+
await ctx.set(event.payload.accountId, {
|
|
84
92
|
isEmailVerified: false,
|
|
85
|
-
passwordHash,
|
|
86
93
|
authMethod: "email",
|
|
87
94
|
registeredAt: event.createdAt,
|
|
88
|
-
lastSignedInAt:
|
|
89
|
-
...
|
|
90
|
-
}
|
|
95
|
+
lastSignedInAt: undefined,
|
|
96
|
+
...event.payload,
|
|
97
|
+
});
|
|
91
98
|
},
|
|
92
99
|
)
|
|
93
100
|
|
|
94
101
|
.publicEvent(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
"accountRegisteredViaOAuth",
|
|
103
|
+
mergeUnsafe(
|
|
104
|
+
{
|
|
105
|
+
accountId,
|
|
106
|
+
email: string().email(),
|
|
107
|
+
provider: string(),
|
|
108
|
+
providerUserId: string(),
|
|
109
|
+
},
|
|
110
|
+
customFields,
|
|
111
|
+
),
|
|
103
112
|
async (ctx, event) => {
|
|
104
|
-
|
|
105
|
-
accountId: id,
|
|
106
|
-
email,
|
|
107
|
-
provider: _provider,
|
|
108
|
-
providerUserId: _providerUserId,
|
|
109
|
-
...customData
|
|
110
|
-
} = event.payload as any;
|
|
111
|
-
await ctx.set(id, {
|
|
112
|
-
email,
|
|
113
|
+
await ctx.set(event.payload.accountId, {
|
|
113
114
|
isEmailVerified: true,
|
|
114
|
-
passwordHash:
|
|
115
|
+
passwordHash: undefined,
|
|
115
116
|
authMethod: "oauth",
|
|
116
117
|
registeredAt: event.createdAt,
|
|
117
118
|
lastSignedInAt: event.createdAt,
|
|
118
|
-
...
|
|
119
|
-
}
|
|
119
|
+
...event.payload,
|
|
120
|
+
});
|
|
120
121
|
},
|
|
121
122
|
)
|
|
122
123
|
|
|
123
124
|
.publicEvent(
|
|
124
|
-
|
|
125
|
+
"signedIn",
|
|
125
126
|
{ accountId, email: string().email() },
|
|
126
127
|
async (ctx, event) => {
|
|
127
|
-
await ctx.modify(
|
|
128
|
-
event.
|
|
129
|
-
|
|
130
|
-
lastSignedInAt: event.createdAt,
|
|
131
|
-
} as any,
|
|
132
|
-
);
|
|
128
|
+
await ctx.modify(event.payload.accountId, {
|
|
129
|
+
lastSignedInAt: event.createdAt,
|
|
130
|
+
});
|
|
133
131
|
},
|
|
134
132
|
)
|
|
135
133
|
|
|
136
|
-
.publicEvent(
|
|
137
|
-
await ctx.modify(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
isEmailVerified: true,
|
|
141
|
-
} as any,
|
|
142
|
-
);
|
|
134
|
+
.publicEvent("emailVerified", { accountId }, async (ctx, event) => {
|
|
135
|
+
await ctx.modify(event.payload.accountId, {
|
|
136
|
+
isEmailVerified: true,
|
|
137
|
+
});
|
|
143
138
|
})
|
|
144
139
|
|
|
145
140
|
// --- Mutate Methods ---
|
|
146
141
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
142
|
+
.mutateMethod("register", (fn) =>
|
|
143
|
+
fn
|
|
144
|
+
.withParams(
|
|
145
|
+
mergeUnsafe(
|
|
146
|
+
{
|
|
147
|
+
email: string().email(),
|
|
148
|
+
password: string().minLength(6).maxLength(32),
|
|
149
|
+
},
|
|
150
|
+
customFields,
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
.handle(
|
|
154
|
+
ONLY_SERVER &&
|
|
155
|
+
(async (ctx, params) => {
|
|
156
|
+
const existing = await ctx.$query.findOne({
|
|
157
|
+
email: params.email,
|
|
158
|
+
});
|
|
159
|
+
if (existing) {
|
|
160
|
+
return { error: "EMAIL_ALREADY_TAKEN" as const };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const id = accountId.generate();
|
|
164
|
+
const pwHash = await hashPassword(params.password);
|
|
165
|
+
|
|
166
|
+
await ctx.accountRegistered.emit({
|
|
167
|
+
...params,
|
|
168
|
+
accountId: id,
|
|
169
|
+
passwordHash: pwHash,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return { accountId: id };
|
|
173
|
+
}),
|
|
174
|
+
),
|
|
179
175
|
)
|
|
180
176
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
.mutateMethod(
|
|
185
|
-
"signIn",
|
|
186
|
-
{
|
|
187
|
-
params: {
|
|
177
|
+
.mutateMethod("signIn", (fn) =>
|
|
178
|
+
fn
|
|
179
|
+
.withParams({
|
|
188
180
|
email: string().email(),
|
|
189
181
|
password: string().minLength(6).maxLength(32),
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
182
|
+
})
|
|
183
|
+
.handle(
|
|
184
|
+
ONLY_SERVER &&
|
|
185
|
+
(async (ctx, params) => {
|
|
186
|
+
const account = await ctx.$query.findOne({
|
|
187
|
+
email: params.email,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (!account) {
|
|
191
|
+
return { error: "INVALID_EMAIL_OR_PASSWORD" as const };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const isValid = await verifyPassword(
|
|
195
|
+
params.password,
|
|
196
|
+
account.passwordHash!,
|
|
197
|
+
);
|
|
198
|
+
if (!isValid) {
|
|
199
|
+
return { error: "INVALID_EMAIL_OR_PASSWORD" as const };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!account.isEmailVerified) {
|
|
203
|
+
return {
|
|
204
|
+
error: "EMAIL_NOT_VERIFIED" as const,
|
|
205
|
+
email: params.email,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const jwtToken = token.generateJWT(buildTokenParams(account));
|
|
210
|
+
|
|
211
|
+
await ctx.signedIn.emit({
|
|
212
|
+
accountId: account._id,
|
|
213
|
+
email: params.email,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return { token: jwtToken };
|
|
217
|
+
}),
|
|
218
|
+
),
|
|
224
219
|
)
|
|
225
220
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
provider: string(),
|
|
235
|
-
providerUserId: string(),
|
|
236
|
-
...customFields,
|
|
237
|
-
},
|
|
238
|
-
result: {} as
|
|
239
|
-
| { accountId: $type<typeof accountId>; token: string }
|
|
240
|
-
| {
|
|
241
|
-
error: "EMAIL_ALREADY_TAKEN";
|
|
242
|
-
accountId: $type<typeof accountId>;
|
|
243
|
-
token: string;
|
|
221
|
+
.mutateMethod("registerViaOAuth", (fn) =>
|
|
222
|
+
fn
|
|
223
|
+
.withParams(
|
|
224
|
+
mergeUnsafe(
|
|
225
|
+
{
|
|
226
|
+
email: string().email(),
|
|
227
|
+
provider: string(),
|
|
228
|
+
providerUserId: string(),
|
|
244
229
|
},
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
230
|
+
customFields,
|
|
231
|
+
),
|
|
232
|
+
)
|
|
233
|
+
.handle(
|
|
234
|
+
ONLY_SERVER &&
|
|
235
|
+
(async (ctx, params) => {
|
|
236
|
+
const existing = await ctx.$query.findOne({
|
|
237
|
+
email: params.email,
|
|
238
|
+
});
|
|
239
|
+
if (existing) {
|
|
240
|
+
return {
|
|
241
|
+
error: "EMAIL_ALREADY_TAKEN" as const,
|
|
242
|
+
accountId: existing._id,
|
|
243
|
+
token: token.generateJWT(buildTokenParams(existing)),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const id = accountId.generate();
|
|
248
|
+
|
|
249
|
+
await ctx.accountRegisteredViaOAuth.emit({
|
|
250
|
+
...params,
|
|
251
|
+
accountId: id,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const newAccount = await ctx.$query.findOne({ _id: id });
|
|
255
|
+
return {
|
|
256
|
+
accountId: id,
|
|
257
|
+
token: token.generateJWT(
|
|
258
|
+
buildTokenParams(newAccount ?? { _id: id }),
|
|
259
|
+
),
|
|
260
|
+
};
|
|
261
|
+
}),
|
|
262
|
+
),
|
|
275
263
|
)
|
|
276
264
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
*/
|
|
281
|
-
.mutateMethod(
|
|
282
|
-
"signInViaOAuth",
|
|
283
|
-
{
|
|
284
|
-
params: {
|
|
265
|
+
.mutateMethod("signInViaOAuth", (fn) =>
|
|
266
|
+
fn
|
|
267
|
+
.withParams({
|
|
285
268
|
email: string().email(),
|
|
286
269
|
provider: string(),
|
|
287
270
|
providerUserId: string(),
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
271
|
+
})
|
|
272
|
+
.handle(
|
|
273
|
+
ONLY_SERVER &&
|
|
274
|
+
(async (ctx, params) => {
|
|
275
|
+
const account = await ctx.$query.findOne({
|
|
276
|
+
email: params.email,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (!account) {
|
|
280
|
+
return { error: "ACCOUNT_NOT_FOUND" as const };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const jwtToken = token.generateJWT(buildTokenParams(account));
|
|
284
|
+
|
|
285
|
+
await ctx.signedIn.emit({
|
|
286
|
+
accountId: account._id,
|
|
287
|
+
email: params.email,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return { token: jwtToken };
|
|
291
|
+
}),
|
|
292
|
+
),
|
|
308
293
|
)
|
|
309
294
|
|
|
310
|
-
.protectBy(token, (params
|
|
311
|
-
.clientQuery("getAll",
|
|
312
|
-
|
|
295
|
+
.protectBy(token, (params) => ({ _id: params.accountId }))
|
|
296
|
+
.clientQuery("getAll", (fn) =>
|
|
297
|
+
fn.handle(async (ctx) => ctx.$query.find({})),
|
|
298
|
+
)
|
|
299
|
+
.clientQuery("getMe", (fn) =>
|
|
300
|
+
fn.handle(async (ctx) => ctx.$query.findOne({})),
|
|
301
|
+
)
|
|
313
302
|
);
|
|
314
303
|
};
|
|
315
304
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { aggregate, date, string } from "@arcote.tech/arc";
|
|
2
|
-
import type { OAuthIdentityId } from "../ids/oauth-identity";
|
|
3
2
|
import type { AccountId } from "../ids/account";
|
|
3
|
+
import type { OAuthIdentityId } from "../ids/oauth-identity";
|
|
4
4
|
|
|
5
5
|
export type OAuthIdentityAggregateData = {
|
|
6
6
|
name: string;
|
|
@@ -14,7 +14,7 @@ export const createOAuthIdentityAggregate = <
|
|
|
14
14
|
data: Data,
|
|
15
15
|
) => {
|
|
16
16
|
const { oauthIdentityId, accountId } = data;
|
|
17
|
-
const name = data.name as Data[
|
|
17
|
+
const name = data.name as Data["name"];
|
|
18
18
|
|
|
19
19
|
return aggregate(`${name}OAuthIdentities`, oauthIdentityId, {
|
|
20
20
|
accountId,
|
|
@@ -25,7 +25,7 @@ export const createOAuthIdentityAggregate = <
|
|
|
25
25
|
lastUsedAt: date().optional(),
|
|
26
26
|
})
|
|
27
27
|
.publicEvent(
|
|
28
|
-
|
|
28
|
+
"oauthIdentityLinked",
|
|
29
29
|
{
|
|
30
30
|
oauthIdentityId,
|
|
31
31
|
accountId,
|
|
@@ -40,90 +40,94 @@ export const createOAuthIdentityAggregate = <
|
|
|
40
40
|
provider,
|
|
41
41
|
providerUserId,
|
|
42
42
|
providerEmail,
|
|
43
|
-
} = event.payload
|
|
43
|
+
} = event.payload;
|
|
44
44
|
await ctx.set(id, {
|
|
45
45
|
accountId: accId,
|
|
46
46
|
provider,
|
|
47
47
|
providerUserId,
|
|
48
48
|
providerEmail,
|
|
49
49
|
linkedAt: event.createdAt,
|
|
50
|
-
lastUsedAt:
|
|
51
|
-
}
|
|
50
|
+
lastUsedAt: undefined,
|
|
51
|
+
});
|
|
52
52
|
},
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
.publicEvent(
|
|
56
|
-
|
|
56
|
+
"oauthIdentityUsed",
|
|
57
57
|
{ oauthIdentityId },
|
|
58
58
|
async (ctx, event) => {
|
|
59
|
-
await ctx.modify(event.payload.oauthIdentityId
|
|
59
|
+
await ctx.modify(event.payload.oauthIdentityId, {
|
|
60
60
|
lastUsedAt: event.createdAt,
|
|
61
|
-
}
|
|
61
|
+
});
|
|
62
62
|
},
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
.publicEvent(
|
|
66
|
-
|
|
66
|
+
"oauthIdentityUnlinked",
|
|
67
67
|
{ oauthIdentityId },
|
|
68
68
|
async (ctx, event) => {
|
|
69
|
-
await ctx.remove(event.payload.oauthIdentityId
|
|
69
|
+
await ctx.remove(event.payload.oauthIdentityId);
|
|
70
70
|
},
|
|
71
71
|
)
|
|
72
72
|
|
|
73
|
-
.mutateMethod(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
params: {
|
|
73
|
+
.mutateMethod("linkIdentity", (fn) =>
|
|
74
|
+
fn
|
|
75
|
+
.withParams({
|
|
77
76
|
accountId,
|
|
78
77
|
provider: string(),
|
|
79
78
|
providerUserId: string(),
|
|
80
79
|
providerEmail: string().email(),
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
})
|
|
81
|
+
.handle(
|
|
82
|
+
ONLY_SERVER &&
|
|
83
|
+
(async (ctx, params) => {
|
|
84
|
+
const existing = await ctx.$query.findOne({
|
|
85
|
+
provider: params.provider,
|
|
86
|
+
providerUserId: params.providerUserId,
|
|
87
|
+
});
|
|
88
|
+
if (existing) {
|
|
89
|
+
return { error: "OAUTH_IDENTITY_ALREADY_LINKED" as const };
|
|
90
|
+
}
|
|
92
91
|
|
|
93
|
-
|
|
92
|
+
const id = oauthIdentityId.generate();
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
await ctx.oauthIdentityLinked.emit({
|
|
95
|
+
oauthIdentityId: id,
|
|
96
|
+
accountId: params.accountId,
|
|
97
|
+
provider: params.provider,
|
|
98
|
+
providerUserId: params.providerUserId,
|
|
99
|
+
providerEmail: params.providerEmail,
|
|
100
|
+
});
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
return { oauthIdentityId: id };
|
|
103
|
+
}),
|
|
104
|
+
),
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
-
.mutateMethod(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
})
|
|
115
|
-
|
|
107
|
+
.mutateMethod("markUsed", (fn) =>
|
|
108
|
+
fn.withParams({ oauthIdentityId }).handle(
|
|
109
|
+
ONLY_SERVER &&
|
|
110
|
+
(async (ctx, params) => {
|
|
111
|
+
await ctx["oauthIdentityUsed"].emit({
|
|
112
|
+
oauthIdentityId: params.oauthIdentityId,
|
|
113
|
+
});
|
|
114
|
+
}),
|
|
115
|
+
),
|
|
116
116
|
)
|
|
117
|
-
.clientQuery("getAll",
|
|
118
|
-
|
|
119
|
-
"findByProvider",
|
|
120
|
-
async (ctx, params: { provider: string; providerUserId: string }) =>
|
|
121
|
-
ctx.$query.findOne({
|
|
122
|
-
provider: params.provider,
|
|
123
|
-
providerUserId: params.providerUserId,
|
|
124
|
-
}),
|
|
117
|
+
.clientQuery("getAll", (fn) =>
|
|
118
|
+
fn.handle(async (ctx) => ctx.$query.find({})),
|
|
125
119
|
)
|
|
126
|
-
|
|
120
|
+
.clientQuery("findByProvider", (fn) =>
|
|
121
|
+
fn
|
|
122
|
+
.withParams({ provider: string(), providerUserId: string() })
|
|
123
|
+
.handle(
|
|
124
|
+
async (ctx, params: { provider: string; providerUserId: string }) =>
|
|
125
|
+
ctx.$query.findOne({
|
|
126
|
+
provider: params.provider,
|
|
127
|
+
providerUserId: params.providerUserId,
|
|
128
|
+
}),
|
|
129
|
+
),
|
|
130
|
+
);
|
|
127
131
|
};
|
|
128
132
|
|
|
129
133
|
export type OAuthIdentityAggregate<
|
package/src/auth-builder.ts
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
context,
|
|
3
3
|
route,
|
|
4
|
-
type ArcAggregateElement,
|
|
5
4
|
type ArcContextElement,
|
|
6
5
|
type ArcRawShape,
|
|
7
6
|
} from "@arcote.tech/arc";
|
|
7
|
+
import type { AccountAggregate } from "./aggregates/account";
|
|
8
|
+
import type { OAuthIdentityAggregate } from "./aggregates/oauth-identity";
|
|
8
9
|
import { createAccountAggregate } from "./aggregates/account";
|
|
9
10
|
import { createOAuthIdentityAggregate } from "./aggregates/oauth-identity";
|
|
10
11
|
import { createAccountId } from "./ids/account";
|
|
11
12
|
import { createOAuthIdentityId } from "./ids/oauth-identity";
|
|
12
13
|
import { createToken } from "./tokens/token";
|
|
13
14
|
import { createOAuthRoutes } from "./routes/oauth-routes";
|
|
15
|
+
import type { AccountId } from "./ids/account";
|
|
14
16
|
import type { OAuthProvidersConfig } from "./providers/types";
|
|
17
|
+
import type { Token } from "./tokens/token";
|
|
15
18
|
|
|
16
19
|
export class AuthBuilder<
|
|
17
|
-
AccId,
|
|
18
|
-
Tok,
|
|
19
|
-
Account extends
|
|
20
|
-
OAuthIdentity extends
|
|
20
|
+
AccId extends AccountId,
|
|
21
|
+
Tok extends Token,
|
|
22
|
+
Account extends AccountAggregate,
|
|
23
|
+
OAuthIdentity extends OAuthIdentityAggregate | undefined,
|
|
21
24
|
EnabledProviders extends string[],
|
|
22
25
|
Elements extends ArcContextElement<any>[],
|
|
23
26
|
> {
|
|
@@ -39,7 +42,7 @@ export class AuthBuilder<
|
|
|
39
42
|
const OAuthIdentity = createOAuthIdentityAggregate({
|
|
40
43
|
name: this._name,
|
|
41
44
|
oauthIdentityId,
|
|
42
|
-
accountId: this.accountId
|
|
45
|
+
accountId: this.accountId,
|
|
43
46
|
});
|
|
44
47
|
|
|
45
48
|
const hasProviders = config.providers && config.baseUrl;
|
|
@@ -47,7 +50,7 @@ export class AuthBuilder<
|
|
|
47
50
|
? createOAuthRoutes({
|
|
48
51
|
providers: config.providers!,
|
|
49
52
|
baseUrl: config.baseUrl!,
|
|
50
|
-
token: this.token
|
|
53
|
+
token: this.token,
|
|
51
54
|
accountElement: this.Account,
|
|
52
55
|
oauthIdentityElement: OAuthIdentity,
|
|
53
56
|
})
|
|
@@ -248,9 +248,13 @@ export function createOAuthRoutes(data: OAuthRoutesData) {
|
|
|
248
248
|
const targetUrl = new URL(decodedRedirect, baseUrl);
|
|
249
249
|
targetUrl.searchParams.set("token", jwtToken);
|
|
250
250
|
|
|
251
|
-
//
|
|
251
|
+
// Set auth cookie + clear state cookie
|
|
252
252
|
const response = Response.redirect(targetUrl.toString(), 302);
|
|
253
253
|
const headers = new Headers(response.headers);
|
|
254
|
+
headers.append(
|
|
255
|
+
"Set-Cookie",
|
|
256
|
+
`arc_token=${encodeURIComponent(jwtToken)}; HttpOnly; SameSite=Lax; Path=/; Max-Age=604800`,
|
|
257
|
+
);
|
|
254
258
|
headers.append(
|
|
255
259
|
"Set-Cookie",
|
|
256
260
|
`oauth_state=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`,
|
package/src/tokens/token.ts
CHANGED
|
@@ -10,7 +10,7 @@ export const createToken = <const Data extends TokenData>(data: Data) =>
|
|
|
10
10
|
token(`${data.name}Account`, {
|
|
11
11
|
accountId: string(),
|
|
12
12
|
...(data.extraParams ?? {}),
|
|
13
|
-
}
|
|
13
|
+
}).secret(data.secret);
|
|
14
14
|
|
|
15
15
|
export type Token<Data extends TokenData = TokenData> = ReturnType<
|
|
16
16
|
typeof createToken<Data>
|