@gasboost/auth 0.1.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/README.md +484 -0
- package/dist/AppsScriptAuth.d.ts +51 -0
- package/dist/AppsScriptAuth.js +77 -0
- package/dist/AuthPattern.d.ts +5 -0
- package/dist/AuthPattern.js +4 -0
- package/dist/api/AppsScriptAuthPassword.d.ts +30 -0
- package/dist/api/AppsScriptAuthPassword.js +64 -0
- package/dist/api/AppsScriptAuthSession.d.ts +10 -0
- package/dist/api/AppsScriptAuthSession.js +17 -0
- package/dist/api/AppsScriptAuthSignIn.d.ts +20 -0
- package/dist/api/AppsScriptAuthSignIn.js +34 -0
- package/dist/api/AppsScriptAuthSignOut.d.ts +7 -0
- package/dist/api/AppsScriptAuthSignOut.js +9 -0
- package/dist/api/AppsScriptAuthSignUp.d.ts +20 -0
- package/dist/api/AppsScriptAuthSignUp.js +35 -0
- package/dist/api/SignIn.d.ts +19 -0
- package/dist/api/SignIn.js +26 -0
- package/dist/api/SignUp.d.ts +20 -0
- package/dist/api/SignUp.js +25 -0
- package/dist/authentication/AppsScriptAuthentication.d.ts +24 -0
- package/dist/authentication/AppsScriptAuthentication.js +47 -0
- package/dist/authentication/Authentication.d.ts +4 -0
- package/dist/authentication/Authentication.js +1 -0
- package/dist/authentication/EmailPasswordAuthentication.d.ts +42 -0
- package/dist/authentication/EmailPasswordAuthentication.js +75 -0
- package/dist/domain/Account.d.ts +11 -0
- package/dist/domain/Account.js +10 -0
- package/dist/domain/Errors.d.ts +3 -0
- package/dist/domain/Errors.js +5 -0
- package/dist/domain/Password.d.ts +15 -0
- package/dist/domain/Password.js +54 -0
- package/dist/domain/PasswordCredential.d.ts +25 -0
- package/dist/domain/PasswordCredential.js +57 -0
- package/dist/domain/PasswordReset.d.ts +25 -0
- package/dist/domain/PasswordReset.js +44 -0
- package/dist/domain/PasswordResetToken.d.ts +6 -0
- package/dist/domain/PasswordResetToken.js +17 -0
- package/dist/domain/Session.d.ts +14 -0
- package/dist/domain/Session.js +15 -0
- package/dist/domain/User.d.ts +13 -0
- package/dist/domain/User.js +13 -0
- package/dist/factory/sessionStorageFactory.d.ts +6 -0
- package/dist/factory/sessionStorageFactory.js +11 -0
- package/dist/identity/AppsScriptIdentity.d.ts +8 -0
- package/dist/identity/AppsScriptIdentity.js +8 -0
- package/dist/identity/EmailPasswordIdentity.d.ts +13 -0
- package/dist/identity/EmailPasswordIdentity.js +19 -0
- package/dist/identity/ProviderIdentity.d.ts +5 -0
- package/dist/identity/ProviderIdentity.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +10 -0
- package/dist/registration/AppsScriptRegistration.d.ts +13 -0
- package/dist/registration/AppsScriptRegistration.js +46 -0
- package/dist/registration/EmailPasswordRegistration.d.ts +16 -0
- package/dist/registration/EmailPasswordRegistration.js +41 -0
- package/dist/registration/Registration.d.ts +4 -0
- package/dist/registration/Registration.js +1 -0
- package/dist/schema/AuthSchema.d.ts +64 -0
- package/dist/schema/AuthSchema.js +35 -0
- package/dist/storage/AppsScriptAuthRepository.d.ts +17 -0
- package/dist/storage/AppsScriptAuthRepository.js +1 -0
- package/dist/storage/AppsScriptCacheSessionStorage.d.ts +10 -0
- package/dist/storage/AppsScriptCacheSessionStorage.js +30 -0
- package/dist/storage/AppsScriptPropertiesSessionStorage.d.ts +11 -0
- package/dist/storage/AppsScriptPropertiesSessionStorage.js +51 -0
- package/dist/storage/AppsScriptSessionStorage.d.ts +7 -0
- package/dist/storage/AppsScriptSessionStorage.js +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# @gasboost/auth
|
|
2
|
+
|
|
3
|
+
Google Apps Script 向けの認証ライブラリです。
|
|
4
|
+
|
|
5
|
+
`@gasboost/auth` は、GAS Web App の公開設定とは独立して、アプリケーション上の User / Account / Identity / Session を扱います。
|
|
6
|
+
|
|
7
|
+
初期実装では以下の認証方式を提供します。
|
|
8
|
+
|
|
9
|
+
- Email / Password
|
|
10
|
+
- Apps Script Active User
|
|
11
|
+
|
|
12
|
+
また、Session の保存先として以下を利用できます。
|
|
13
|
+
|
|
14
|
+
- `CacheService`
|
|
15
|
+
- `PropertiesService`
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add @gasboost/auth
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
npm:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @gasboost/auth
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Concepts
|
|
30
|
+
|
|
31
|
+
認証 Identity と Application User は直接同一視しません。
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
ProviderIdentity
|
|
35
|
+
↓
|
|
36
|
+
Account
|
|
37
|
+
↓
|
|
38
|
+
User
|
|
39
|
+
↓
|
|
40
|
+
Session
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
1つの User に複数の Account を紐付けることができます。
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
User
|
|
47
|
+
├─ Account(emailPassword)
|
|
48
|
+
└─ Account(appsScript)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Identity は `(provider, accountId)` の組み合わせで識別します。
|
|
52
|
+
|
|
53
|
+
そのため、同じメールアドレスでも認証方式が異なれば別の Identity として扱えます。
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
(emailPassword, user@example.com)
|
|
57
|
+
(appsScript, user@example.com)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Repository
|
|
61
|
+
|
|
62
|
+
`@gasboost/auth` は特定の Database / ORM に依存しません。
|
|
63
|
+
|
|
64
|
+
利用側で `AppsScriptAuthRepository` を実装して注入します。
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import type { AppsScriptAuthRepository } from "@gasboost/auth";
|
|
68
|
+
|
|
69
|
+
const repository: AppsScriptAuthRepository = {
|
|
70
|
+
account: {
|
|
71
|
+
async findByIdentity(provider, identifier) {
|
|
72
|
+
// Account を検索
|
|
73
|
+
return null;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
user: {
|
|
78
|
+
async find(id) {
|
|
79
|
+
// User を検索
|
|
80
|
+
return null;
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async create(user) {
|
|
84
|
+
// User + Account を保存
|
|
85
|
+
return user;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
将来的に SheetORM 向け adapter は auth-core とは別 package として提供する想定です。
|
|
92
|
+
|
|
93
|
+
## Initialize
|
|
94
|
+
|
|
95
|
+
`AppsScriptAuth` に Repository、GAS runtime、Session 設定、認証方式の設定を渡します。
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { AppsScriptAuth } from "@gasboost/auth";
|
|
99
|
+
|
|
100
|
+
const pepper =
|
|
101
|
+
PropertiesService.getScriptProperties().getProperty("AUTH_PEPPER") ?? "";
|
|
102
|
+
|
|
103
|
+
const auth = new AppsScriptAuth({
|
|
104
|
+
repository,
|
|
105
|
+
|
|
106
|
+
runtime: {
|
|
107
|
+
utilities: Utilities,
|
|
108
|
+
session: Session,
|
|
109
|
+
cacheService: CacheService,
|
|
110
|
+
propertiesService: PropertiesService,
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
session: {
|
|
114
|
+
storageType: "cache",
|
|
115
|
+
expiresIn: 60 * 20,
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
emailPassword: {
|
|
119
|
+
enabled: true,
|
|
120
|
+
isSignupEnabled: true,
|
|
121
|
+
pepper,
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
appsScript: {
|
|
125
|
+
enabled: true,
|
|
126
|
+
isSignupEnabled: true,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`runtime` を constructor injection するため、auth-core 内部では GAS global を直接参照しません。
|
|
132
|
+
|
|
133
|
+
これにより、テスト環境では `@gasboost/fake-core` / `@gasboost/fake-node` などの fake implementation を注入できます。
|
|
134
|
+
|
|
135
|
+
## Session Storage
|
|
136
|
+
|
|
137
|
+
### CacheService
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
session: {
|
|
141
|
+
storageType: "cache",
|
|
142
|
+
expiresIn: 60 * 20,
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
CacheService の TTL を利用して Session を保持します。
|
|
147
|
+
|
|
148
|
+
低レイテンシな Session Storage として利用できます。
|
|
149
|
+
|
|
150
|
+
### PropertiesService
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
session: {
|
|
154
|
+
storageType: "properties",
|
|
155
|
+
expiresIn: 60 * 20,
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
PropertiesService では Session を以下の key 形式で保存します。
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
session:<sessionId>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
そのため、同じ Properties に保存された `API_KEY` などの別データとは分離されます。
|
|
166
|
+
|
|
167
|
+
期限切れ Session は `cleanup()` で削除できます。
|
|
168
|
+
|
|
169
|
+
## Email / Password Sign Up
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const { user, session } = await auth.signUp.email({
|
|
173
|
+
name: "Taro",
|
|
174
|
+
email: "user@example.com",
|
|
175
|
+
password: "password",
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
処理フロー:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
email identity の重複確認
|
|
183
|
+
↓
|
|
184
|
+
password hash
|
|
185
|
+
↓
|
|
186
|
+
EmailPasswordIdentity
|
|
187
|
+
↓
|
|
188
|
+
Account
|
|
189
|
+
↓
|
|
190
|
+
User
|
|
191
|
+
↓
|
|
192
|
+
Session 発行
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Email / Password Sign In
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
const { user, session } = await auth.signIn.email({
|
|
199
|
+
email: "user@example.com",
|
|
200
|
+
password: "password",
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
処理フロー:
|
|
205
|
+
|
|
206
|
+
```text
|
|
207
|
+
email
|
|
208
|
+
↓
|
|
209
|
+
Account 検索
|
|
210
|
+
↓
|
|
211
|
+
password verify
|
|
212
|
+
↓
|
|
213
|
+
User 検索
|
|
214
|
+
↓
|
|
215
|
+
Session 発行
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Apps Script Sign Up
|
|
219
|
+
|
|
220
|
+
Apps Script 認証では `Session.getActiveUser().getEmail()` を Identity として利用します。
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
const { user, session } = await auth.signUp.appsScript({
|
|
224
|
+
name: "Taro",
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Active User の email を取得できない場合は登録できません。
|
|
229
|
+
|
|
230
|
+
## Apps Script Sign In
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
const { user, session } = await auth.signIn.appsScript({});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
処理フロー:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
Session.getActiveUser().getEmail()
|
|
240
|
+
↓
|
|
241
|
+
AppsScriptIdentity
|
|
242
|
+
↓
|
|
243
|
+
Account 検索
|
|
244
|
+
↓
|
|
245
|
+
User 検索
|
|
246
|
+
↓
|
|
247
|
+
Session 発行
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Session
|
|
251
|
+
|
|
252
|
+
### Get Session
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
const session = await auth.session.get(sessionId);
|
|
256
|
+
|
|
257
|
+
if (!session) {
|
|
258
|
+
// Session が存在しない、または期限切れ
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Session は以下の情報を持ちます。
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
session.id;
|
|
266
|
+
session.userId;
|
|
267
|
+
session.createdAt;
|
|
268
|
+
session.expiresAt;
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Cleanup Expired Sessions
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
await auth.session.cleanup();
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`PropertiesService` 使用時は期限切れ Session を削除します。
|
|
278
|
+
|
|
279
|
+
`CacheService` は自身で expiration を管理するため、cleanup は no-op です。
|
|
280
|
+
|
|
281
|
+
## Sign Out
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
await auth.signOut.execute(sessionId);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
対象 Session を SessionStorage から削除します。
|
|
288
|
+
|
|
289
|
+
## Configuration
|
|
290
|
+
|
|
291
|
+
### Email / Password
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
emailPassword: {
|
|
295
|
+
enabled: true,
|
|
296
|
+
isSignupEnabled: true,
|
|
297
|
+
pepper: "...",
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`enabled` が `true` の場合、`pepper` は必須です。
|
|
302
|
+
|
|
303
|
+
空文字または空白だけの pepper を指定すると `AppsScriptAuth` の初期化時に失敗します。
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
new AppsScriptAuth({
|
|
307
|
+
// ...
|
|
308
|
+
emailPassword: {
|
|
309
|
+
enabled: true,
|
|
310
|
+
pepper: "",
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
Pepper is required when email and password authentication is enabled
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`isSignupEnabled` を `false` にすると Sign In は許可したまま Sign Up だけを無効化できます。
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
emailPassword: {
|
|
323
|
+
enabled: true,
|
|
324
|
+
isSignupEnabled: false,
|
|
325
|
+
pepper: "...",
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Apps Script
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
appsScript: {
|
|
333
|
+
enabled: true,
|
|
334
|
+
isSignupEnabled: true,
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
こちらも Sign In と Sign Up を個別に制御できます。
|
|
339
|
+
|
|
340
|
+
## Password
|
|
341
|
+
|
|
342
|
+
Email / Password 認証では以下を利用します。
|
|
343
|
+
|
|
344
|
+
- per-password salt
|
|
345
|
+
- application pepper
|
|
346
|
+
- iterations
|
|
347
|
+
- HMAC-SHA256 を利用した反復処理
|
|
348
|
+
|
|
349
|
+
現在の default iterations は `300` です。
|
|
350
|
+
|
|
351
|
+
pepper は User / Account の保存先とは分離し、Script Properties などで管理することを推奨します。
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
const pepper =
|
|
355
|
+
PropertiesService.getScriptProperties().getProperty("AUTH_PEPPER");
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## `@gasboost/app` との連携
|
|
359
|
+
|
|
360
|
+
`AppsScriptAuth` は、RPC 登録用の handler collection を `auth.handlers` として公開します。
|
|
361
|
+
|
|
362
|
+
`@gasboost/app` の `.calls()` にそのまま渡すことで、認証用 RPC を一括登録できます。
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
import { AppsScript } from "@gasboost/app";
|
|
366
|
+
import { AppsScriptAuth } from "@gasboost/auth";
|
|
367
|
+
|
|
368
|
+
const auth = new AppsScriptAuth({
|
|
369
|
+
repository,
|
|
370
|
+
runtime,
|
|
371
|
+
session: {
|
|
372
|
+
storageType: "cache",
|
|
373
|
+
},
|
|
374
|
+
emailPassword: {
|
|
375
|
+
enabled: true,
|
|
376
|
+
pepper: "your-secret-pepper",
|
|
377
|
+
},
|
|
378
|
+
appsScript: {
|
|
379
|
+
enabled: true,
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const app = new AppsScript().calls(auth.handlers);
|
|
384
|
+
|
|
385
|
+
export default app;
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
以下の RPC が登録されます。
|
|
389
|
+
|
|
390
|
+
- `signInEmail`
|
|
391
|
+
- `signInAppsScript`
|
|
392
|
+
- `signUpEmail`
|
|
393
|
+
- `signUpAppsScript`
|
|
394
|
+
- `getSession`
|
|
395
|
+
- `signOut`
|
|
396
|
+
|
|
397
|
+
`InferAppsScript` を利用するクライアントでは、これらの RPC がそのまま型推論されます。
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
client.signInEmail({
|
|
401
|
+
email: "user@example.com",
|
|
402
|
+
password: "password",
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
client.getSession("session-id");
|
|
406
|
+
|
|
407
|
+
client.signOut("session-id");
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
RPC endpoint 名は `@gasboost/auth` 側で管理されるため、利用側で個別に `.call()` を記述したり、endpoint 名を重複定義したりする必要はありません。
|
|
411
|
+
|
|
412
|
+
## Testing
|
|
413
|
+
|
|
414
|
+
GAS API を利用するテストでは `gasboost/fake` を使用できます。
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
pnpm add -D \
|
|
418
|
+
@gasboost/fake-core \
|
|
419
|
+
@gasboost/fake-node
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
例:
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import {
|
|
426
|
+
InMemoryCacheService,
|
|
427
|
+
InMemoryPropertiesService,
|
|
428
|
+
InMemorySession,
|
|
429
|
+
} from "@gasboost/fake-core";
|
|
430
|
+
|
|
431
|
+
import { NodeUtilities } from "@gasboost/fake-node";
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
`AppsScriptAuth` の runtime に fake implementation をそのまま注入できます。
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
const auth = new AppsScriptAuth({
|
|
438
|
+
repository,
|
|
439
|
+
|
|
440
|
+
runtime: {
|
|
441
|
+
utilities: new NodeUtilities(),
|
|
442
|
+
session: fakeSession,
|
|
443
|
+
cacheService: new InMemoryCacheService(),
|
|
444
|
+
propertiesService: new InMemoryPropertiesService(),
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
session: {
|
|
448
|
+
storageType: "cache",
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## Scope
|
|
454
|
+
|
|
455
|
+
現在の auth-core が提供するもの:
|
|
456
|
+
|
|
457
|
+
- User / Account / Session
|
|
458
|
+
- ProviderIdentity
|
|
459
|
+
- Email / Password authentication
|
|
460
|
+
- Apps Script Active User authentication
|
|
461
|
+
- Sign In
|
|
462
|
+
- Sign Up
|
|
463
|
+
- Sign Out
|
|
464
|
+
- Session management
|
|
465
|
+
- CacheService SessionStorage
|
|
466
|
+
- PropertiesService SessionStorage
|
|
467
|
+
- GAS runtime dependency injection
|
|
468
|
+
|
|
469
|
+
以下は auth-core の責務外として後続 package / Issue で扱います。
|
|
470
|
+
|
|
471
|
+
- Schema / modelName / field mapping
|
|
472
|
+
- database session storage
|
|
473
|
+
- plugin system
|
|
474
|
+
- admin plugin
|
|
475
|
+
- Google OAuth
|
|
476
|
+
- organization / multi-tenant
|
|
477
|
+
- permission model
|
|
478
|
+
- MCP OAuth
|
|
479
|
+
- frontend UI
|
|
480
|
+
- `@gasboost/app` middleware integration
|
|
481
|
+
|
|
482
|
+
## License
|
|
483
|
+
|
|
484
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AppsScriptAuthPassword } from "./api/AppsScriptAuthPassword";
|
|
2
|
+
import { AppsScriptAuthSession } from "./api/AppsScriptAuthSession";
|
|
3
|
+
import { AppsScriptAuthSignIn } from "./api/AppsScriptAuthSignIn";
|
|
4
|
+
import { AppsScriptAuthSignOut } from "./api/AppsScriptAuthSignOut";
|
|
5
|
+
import { AppsScriptAuthSignUp } from "./api/AppsScriptAuthSignUp";
|
|
6
|
+
import { type AppsScriptAuthenticationOptions } from "./authentication/AppsScriptAuthentication";
|
|
7
|
+
import { type EmailPasswordAuthOptions } from "./authentication/EmailPasswordAuthentication";
|
|
8
|
+
import type { SessionStorageType } from "./factory/sessionStorageFactory";
|
|
9
|
+
import { AppsScriptAuthRepository } from "./storage/AppsScriptAuthRepository";
|
|
10
|
+
type SessionConfig = {
|
|
11
|
+
storageType: SessionStorageType;
|
|
12
|
+
expiresIn?: number;
|
|
13
|
+
};
|
|
14
|
+
type AppsScriptRuntime = {
|
|
15
|
+
utilities: GoogleAppsScript.Utilities.Utilities;
|
|
16
|
+
session: GoogleAppsScript.Base.Session;
|
|
17
|
+
cacheService: GoogleAppsScript.Cache.CacheService;
|
|
18
|
+
propertiesService: GoogleAppsScript.Properties.PropertiesService;
|
|
19
|
+
};
|
|
20
|
+
type AppsScriptAuthConfig<TEmailPassword extends EmailPasswordAuthOptions | undefined = undefined> = {
|
|
21
|
+
repository: AppsScriptAuthRepository;
|
|
22
|
+
session: SessionConfig;
|
|
23
|
+
runtime: AppsScriptRuntime;
|
|
24
|
+
emailPassword?: TEmailPassword;
|
|
25
|
+
appsScript?: AppsScriptAuthenticationOptions;
|
|
26
|
+
};
|
|
27
|
+
type BaseHandlers = {
|
|
28
|
+
signInEmail: AppsScriptAuthSignIn["email"];
|
|
29
|
+
signInAppsScript: AppsScriptAuthSignIn["appsScript"];
|
|
30
|
+
signUpEmail: AppsScriptAuthSignUp["email"];
|
|
31
|
+
signUpAppsScript: AppsScriptAuthSignUp["appsScript"];
|
|
32
|
+
getSession: AppsScriptAuthSession["get"];
|
|
33
|
+
signOut: AppsScriptAuthSignOut["execute"];
|
|
34
|
+
};
|
|
35
|
+
type PasswordHandlers = {
|
|
36
|
+
forgotPassword: AppsScriptAuthPassword["forgot"];
|
|
37
|
+
resetPassword: AppsScriptAuthPassword["reset"];
|
|
38
|
+
};
|
|
39
|
+
type AppsScriptAuthHandlers<TEmailPassword> = TEmailPassword extends {
|
|
40
|
+
passwordReset: unknown;
|
|
41
|
+
} ? BaseHandlers & PasswordHandlers : BaseHandlers;
|
|
42
|
+
export declare class AppsScriptAuth<TEmailPassword extends EmailPasswordAuthOptions | undefined = undefined> {
|
|
43
|
+
readonly signIn: AppsScriptAuthSignIn;
|
|
44
|
+
readonly signUp: AppsScriptAuthSignUp;
|
|
45
|
+
readonly session: AppsScriptAuthSession;
|
|
46
|
+
readonly signOut: AppsScriptAuthSignOut;
|
|
47
|
+
readonly password: AppsScriptAuthPassword | undefined;
|
|
48
|
+
constructor({ repository, session, runtime, emailPassword, appsScript, }: AppsScriptAuthConfig<TEmailPassword>);
|
|
49
|
+
get handlers(): AppsScriptAuthHandlers<TEmailPassword>;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AppsScriptAuthPassword } from "./api/AppsScriptAuthPassword";
|
|
2
|
+
import { AppsScriptAuthSession } from "./api/AppsScriptAuthSession";
|
|
3
|
+
import { AppsScriptAuthSignIn } from "./api/AppsScriptAuthSignIn";
|
|
4
|
+
import { AppsScriptAuthSignOut } from "./api/AppsScriptAuthSignOut";
|
|
5
|
+
import { AppsScriptAuthSignUp } from "./api/AppsScriptAuthSignUp";
|
|
6
|
+
import { AppsScriptAuthenticationConfig, } from "./authentication/AppsScriptAuthentication";
|
|
7
|
+
import { EmailPasswordAuthConfig, } from "./authentication/EmailPasswordAuthentication";
|
|
8
|
+
import { sessionStorageFactory } from "./factory/sessionStorageFactory";
|
|
9
|
+
export class AppsScriptAuth {
|
|
10
|
+
signIn;
|
|
11
|
+
signUp;
|
|
12
|
+
session;
|
|
13
|
+
signOut;
|
|
14
|
+
password;
|
|
15
|
+
constructor({ repository, session, runtime, emailPassword, appsScript, }) {
|
|
16
|
+
const sessionStorage = sessionStorageFactory(session.storageType, {
|
|
17
|
+
cacheService: runtime.cacheService,
|
|
18
|
+
propertiesService: runtime.propertiesService,
|
|
19
|
+
});
|
|
20
|
+
const expiresIn = session.expiresIn ?? 60 * 20;
|
|
21
|
+
const emailPasswordConfig = emailPassword
|
|
22
|
+
? new EmailPasswordAuthConfig(emailPassword)
|
|
23
|
+
: undefined;
|
|
24
|
+
const appsScriptConfig = appsScript
|
|
25
|
+
? new AppsScriptAuthenticationConfig(appsScript)
|
|
26
|
+
: undefined;
|
|
27
|
+
this.signIn = new AppsScriptAuthSignIn({
|
|
28
|
+
sessionStorage,
|
|
29
|
+
repository,
|
|
30
|
+
emailPassword: emailPasswordConfig,
|
|
31
|
+
appsScript: appsScriptConfig,
|
|
32
|
+
utilities: runtime.utilities,
|
|
33
|
+
session: runtime.session,
|
|
34
|
+
expiresIn,
|
|
35
|
+
});
|
|
36
|
+
this.signUp = new AppsScriptAuthSignUp({
|
|
37
|
+
sessionStorage,
|
|
38
|
+
expiresIn,
|
|
39
|
+
repository,
|
|
40
|
+
emailPassword: emailPasswordConfig,
|
|
41
|
+
appsScript: appsScriptConfig,
|
|
42
|
+
utilities: runtime.utilities,
|
|
43
|
+
session: runtime.session,
|
|
44
|
+
});
|
|
45
|
+
this.session = new AppsScriptAuthSession({
|
|
46
|
+
sessionStorage,
|
|
47
|
+
});
|
|
48
|
+
this.signOut = new AppsScriptAuthSignOut(sessionStorage);
|
|
49
|
+
if (emailPasswordConfig?.passwordReset) {
|
|
50
|
+
this.password = new AppsScriptAuthPassword({
|
|
51
|
+
repository,
|
|
52
|
+
utilities: runtime.utilities,
|
|
53
|
+
emailPassword: emailPasswordConfig,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.password = undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
get handlers() {
|
|
61
|
+
const handlers = {
|
|
62
|
+
signInEmail: this.signIn.email,
|
|
63
|
+
signInAppsScript: this.signIn.appsScript,
|
|
64
|
+
signUpEmail: this.signUp.email,
|
|
65
|
+
signUpAppsScript: this.signUp.appsScript,
|
|
66
|
+
getSession: this.session.get.bind(this.session),
|
|
67
|
+
signOut: this.signOut.execute.bind(this.signOut),
|
|
68
|
+
...(this.password
|
|
69
|
+
? {
|
|
70
|
+
forgotPassword: this.password.forgot.bind(this.password),
|
|
71
|
+
resetPassword: this.password.reset.bind(this.password),
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
return handlers;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { EmailPasswordAuthConfig } from "../authentication/EmailPasswordAuthentication";
|
|
2
|
+
import type { AppsScriptAuthRepository } from "../storage/AppsScriptAuthRepository";
|
|
3
|
+
export interface PasswordResetDelivery {
|
|
4
|
+
send({ email, token, expiresAt, }: {
|
|
5
|
+
email: string;
|
|
6
|
+
token: string;
|
|
7
|
+
expiresAt: Date;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare class AppsScriptAuthPassword {
|
|
11
|
+
readonly repository: AppsScriptAuthRepository;
|
|
12
|
+
readonly utilities: GoogleAppsScript.Utilities.Utilities;
|
|
13
|
+
readonly emailPassword: EmailPasswordAuthConfig;
|
|
14
|
+
readonly config: {
|
|
15
|
+
expiresIn: number;
|
|
16
|
+
delivery: PasswordResetDelivery;
|
|
17
|
+
};
|
|
18
|
+
constructor({ repository, utilities, emailPassword, }: {
|
|
19
|
+
repository: AppsScriptAuthRepository;
|
|
20
|
+
utilities: GoogleAppsScript.Utilities.Utilities;
|
|
21
|
+
emailPassword: EmailPasswordAuthConfig;
|
|
22
|
+
});
|
|
23
|
+
forgot({ email }: {
|
|
24
|
+
email: string;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
reset({ token, newPassword, }: {
|
|
27
|
+
token: string;
|
|
28
|
+
newPassword: string;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { authPattern } from "../AuthPattern";
|
|
2
|
+
import { InvalidPasswordResetError } from "../domain/Errors";
|
|
3
|
+
import { Password } from "../domain/Password";
|
|
4
|
+
import { PasswordCredential } from "../domain/PasswordCredential";
|
|
5
|
+
import { PasswordResetToken } from "../domain/PasswordResetToken";
|
|
6
|
+
import { EmailPasswordIdentity } from "../identity/EmailPasswordIdentity";
|
|
7
|
+
export class AppsScriptAuthPassword {
|
|
8
|
+
repository;
|
|
9
|
+
utilities;
|
|
10
|
+
emailPassword;
|
|
11
|
+
config;
|
|
12
|
+
constructor({ repository, utilities, emailPassword, }) {
|
|
13
|
+
emailPassword.ensurePasswordResetEnabled();
|
|
14
|
+
this.repository = repository;
|
|
15
|
+
this.utilities = utilities;
|
|
16
|
+
this.emailPassword = emailPassword;
|
|
17
|
+
const passwordReset = emailPassword.passwordReset;
|
|
18
|
+
if (!passwordReset) {
|
|
19
|
+
throw new Error("Password reset is not enabled");
|
|
20
|
+
}
|
|
21
|
+
this.config = {
|
|
22
|
+
expiresIn: passwordReset.expiresIn ?? 60 * 60,
|
|
23
|
+
delivery: passwordReset.delivery,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async forgot({ email }) {
|
|
27
|
+
const account = await this.repository.account.findByIdentity(authPattern.emailPassword, email);
|
|
28
|
+
if (account === null) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!(account.identity instanceof EmailPasswordIdentity)) {
|
|
32
|
+
throw new Error("Invalid account identity");
|
|
33
|
+
}
|
|
34
|
+
const now = new Date();
|
|
35
|
+
const expiresAt = new Date(now.getTime() + this.config.expiresIn * 1000);
|
|
36
|
+
const { credential, token } = PasswordCredential.generate({
|
|
37
|
+
account,
|
|
38
|
+
utilities: this.utilities,
|
|
39
|
+
expiresAt,
|
|
40
|
+
});
|
|
41
|
+
await this.repository.passwordCredential.save(credential);
|
|
42
|
+
await this.config.delivery.send({
|
|
43
|
+
email,
|
|
44
|
+
token,
|
|
45
|
+
expiresAt,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async reset({ token, newPassword, }) {
|
|
49
|
+
const passwordResetToken = new PasswordResetToken(token);
|
|
50
|
+
const credential = await this.repository.passwordCredential.findByResetTokenHash(passwordResetToken.hash(this.utilities));
|
|
51
|
+
if (credential === null) {
|
|
52
|
+
throw new InvalidPasswordResetError();
|
|
53
|
+
}
|
|
54
|
+
const password = new Password(newPassword, this.utilities, this.emailPassword.pepper, this.emailPassword.iterations);
|
|
55
|
+
const hashedPassword = await password.hash(credential.account.id);
|
|
56
|
+
const updatedCredential = credential.resetPassword({
|
|
57
|
+
token,
|
|
58
|
+
newPassword: hashedPassword,
|
|
59
|
+
utilities: this.utilities,
|
|
60
|
+
now: new Date(),
|
|
61
|
+
});
|
|
62
|
+
await this.repository.passwordCredential.save(updatedCredential);
|
|
63
|
+
}
|
|
64
|
+
}
|