@gasboost/auth 0.1.0 → 0.2.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 CHANGED
@@ -247,6 +247,177 @@ User 検索
247
247
  Session 発行
248
248
  ```
249
249
 
250
+ ## Authentication Hooks
251
+
252
+ `@gasboost/auth` は、認証成功後に外部処理を実行するための lifecycle hook を提供します。
253
+
254
+ 現在は `afterSignIn` を利用できます。
255
+
256
+ ```ts
257
+ import { AppsScriptAuth, type EmailPasswordAuthOptions } from "@gasboost/auth";
258
+
259
+ type HookResult = {
260
+ readonly customToken: string;
261
+ };
262
+
263
+ const auth = new AppsScriptAuth<EmailPasswordAuthOptions, HookResult>({
264
+ repository,
265
+ runtime,
266
+
267
+ session: {
268
+ storageType: "cache",
269
+ },
270
+
271
+ emailPassword: {
272
+ enabled: true,
273
+ pepper,
274
+ },
275
+
276
+ hooks: {
277
+ afterSignIn: ({ user, session }) => ({
278
+ customToken: `${user.id}:${session.id}`,
279
+ }),
280
+ },
281
+ });
282
+ ```
283
+
284
+ `afterSignIn` には、認証された `User` と発行済みの `Session` が渡されます。
285
+
286
+ ```ts
287
+ hooks: {
288
+ afterSignIn: ({ user, session }) => {
289
+ user.id;
290
+ session.id;
291
+
292
+ return {
293
+ customToken: "...",
294
+ };
295
+ },
296
+ },
297
+ ```
298
+
299
+ hook の戻り値は Sign In result の `hooks` から取得できます。
300
+
301
+ ```ts
302
+ const result = await auth.signIn.email({
303
+ email: "user@example.com",
304
+ password: "password",
305
+ });
306
+
307
+ result.user;
308
+ result.session;
309
+ result.hooks.customToken;
310
+ ```
311
+
312
+ hook result の型は `AppsScriptAuth` の第2型引数として指定できます。
313
+
314
+ ```ts
315
+ type HookResult = {
316
+ readonly customToken: string;
317
+ };
318
+
319
+ const auth = new AppsScriptAuth<EmailPasswordAuthOptions, HookResult>({
320
+ // ...
321
+ });
322
+ ```
323
+
324
+ hook を設定しない場合、従来どおり Sign In result は `user` と `session` のみを返します。
325
+
326
+ ```ts
327
+ const auth = new AppsScriptAuth({
328
+ repository,
329
+ runtime,
330
+
331
+ session: {
332
+ storageType: "cache",
333
+ },
334
+ });
335
+
336
+ const result = await auth.signIn.appsScript({});
337
+
338
+ result.user;
339
+ result.session;
340
+ ```
341
+
342
+ ### Async Hook
343
+
344
+ `afterSignIn` は async 処理にも対応しています。
345
+
346
+ ```ts
347
+ hooks: {
348
+ afterSignIn: async ({ user, session }) => {
349
+ const value = await createExternalCredential({
350
+ user,
351
+ session,
352
+ });
353
+
354
+ return {
355
+ value,
356
+ };
357
+ },
358
+ },
359
+ ```
360
+
361
+ ### Hook Failure
362
+
363
+ `afterSignIn` は Sign In 処理の一部として扱われます。
364
+
365
+ 処理順序は以下です。
366
+
367
+ ```text
368
+ authentication
369
+
370
+ User
371
+
372
+ Session 発行
373
+
374
+ Session 保存
375
+
376
+ afterSignIn
377
+
378
+ Sign In result
379
+ ```
380
+
381
+ `afterSignIn` で例外が発生した場合、Sign In 全体が失敗します。
382
+
383
+ その際、すでに保存された Session は SessionStorage から削除されます。
384
+
385
+ ```text
386
+ afterSignIn error
387
+
388
+ Session 削除
389
+
390
+ error を呼び出し元へ伝播
391
+ ```
392
+
393
+ これにより、外部認証情報などの生成に失敗したにもかかわらず、Gasboost 側の Session だけが有効な状態になることを防ぎます。
394
+
395
+ ### Package Boundary
396
+
397
+ hooks API 自体は Firebase やその他の外部サービスを認識しません。
398
+
399
+ ```text
400
+ @gasboost/auth
401
+
402
+ afterSignIn
403
+
404
+ external integration
405
+ ```
406
+
407
+ 例えば Firebase Custom Token を発行する場合でも、Firebase 固有の token generation は `@gasboost/auth` の責務には含めません。
408
+
409
+ ```text
410
+ @gasboost/auth
411
+
412
+ afterSignIn
413
+
414
+ Firebase integration package
415
+
416
+ Firebase Custom Token
417
+ ```
418
+
419
+ `@gasboost/auth` は認証 lifecycle と拡張ポイントのみを提供し、外部サービス固有の処理は別 package から hook として接続します。
420
+
250
421
  ## Session
251
422
 
252
423
  ### Get Session
@@ -461,6 +632,7 @@ const auth = new AppsScriptAuth({
461
632
  - Sign In
462
633
  - Sign Up
463
634
  - Sign Out
635
+ - Authentication lifecycle hooks (`afterSignIn`)
464
636
  - Session management
465
637
  - CacheService SessionStorage
466
638
  - PropertiesService SessionStorage
@@ -6,6 +6,7 @@ import { AppsScriptAuthSignUp } from "./api/AppsScriptAuthSignUp";
6
6
  import { type AppsScriptAuthenticationOptions } from "./authentication/AppsScriptAuthentication";
7
7
  import { type EmailPasswordAuthOptions } from "./authentication/EmailPasswordAuthentication";
8
8
  import type { SessionStorageType } from "./factory/sessionStorageFactory";
9
+ import type { AuthHooks } from "./hooks/AuthHooks";
9
10
  import { AppsScriptAuthRepository } from "./storage/AppsScriptAuthRepository";
10
11
  type SessionConfig = {
11
12
  storageType: SessionStorageType;
@@ -17,16 +18,17 @@ type AppsScriptRuntime = {
17
18
  cacheService: GoogleAppsScript.Cache.CacheService;
18
19
  propertiesService: GoogleAppsScript.Properties.PropertiesService;
19
20
  };
20
- type AppsScriptAuthConfig<TEmailPassword extends EmailPasswordAuthOptions | undefined = undefined> = {
21
+ type AppsScriptAuthConfig<TEmailPassword extends EmailPasswordAuthOptions | undefined = undefined, THookResult = undefined> = {
21
22
  repository: AppsScriptAuthRepository;
22
23
  session: SessionConfig;
23
24
  runtime: AppsScriptRuntime;
24
25
  emailPassword?: TEmailPassword;
25
26
  appsScript?: AppsScriptAuthenticationOptions;
27
+ hooks?: AuthHooks<THookResult>;
26
28
  };
27
- type BaseHandlers = {
28
- signInEmail: AppsScriptAuthSignIn["email"];
29
- signInAppsScript: AppsScriptAuthSignIn["appsScript"];
29
+ type BaseHandlers<THookResult> = {
30
+ signInEmail: AppsScriptAuthSignIn<THookResult>["email"];
31
+ signInAppsScript: AppsScriptAuthSignIn<THookResult>["appsScript"];
30
32
  signUpEmail: AppsScriptAuthSignUp["email"];
31
33
  signUpAppsScript: AppsScriptAuthSignUp["appsScript"];
32
34
  getSession: AppsScriptAuthSession["get"];
@@ -36,16 +38,16 @@ type PasswordHandlers = {
36
38
  forgotPassword: AppsScriptAuthPassword["forgot"];
37
39
  resetPassword: AppsScriptAuthPassword["reset"];
38
40
  };
39
- type AppsScriptAuthHandlers<TEmailPassword> = TEmailPassword extends {
41
+ type AppsScriptAuthHandlers<TEmailPassword, THookResult> = TEmailPassword extends {
40
42
  passwordReset: unknown;
41
- } ? BaseHandlers & PasswordHandlers : BaseHandlers;
42
- export declare class AppsScriptAuth<TEmailPassword extends EmailPasswordAuthOptions | undefined = undefined> {
43
- readonly signIn: AppsScriptAuthSignIn;
43
+ } ? BaseHandlers<THookResult> & PasswordHandlers : BaseHandlers<THookResult>;
44
+ export declare class AppsScriptAuth<TEmailPassword extends EmailPasswordAuthOptions | undefined = undefined, THookResult = undefined> {
45
+ readonly signIn: AppsScriptAuthSignIn<THookResult>;
44
46
  readonly signUp: AppsScriptAuthSignUp;
45
47
  readonly session: AppsScriptAuthSession;
46
48
  readonly signOut: AppsScriptAuthSignOut;
47
49
  readonly password: AppsScriptAuthPassword | undefined;
48
- constructor({ repository, session, runtime, emailPassword, appsScript, }: AppsScriptAuthConfig<TEmailPassword>);
49
- get handlers(): AppsScriptAuthHandlers<TEmailPassword>;
50
+ constructor({ repository, session, runtime, emailPassword, appsScript, hooks, }: AppsScriptAuthConfig<TEmailPassword, THookResult>);
51
+ get handlers(): AppsScriptAuthHandlers<TEmailPassword, THookResult>;
50
52
  }
51
53
  export {};
@@ -12,7 +12,7 @@ export class AppsScriptAuth {
12
12
  session;
13
13
  signOut;
14
14
  password;
15
- constructor({ repository, session, runtime, emailPassword, appsScript, }) {
15
+ constructor({ repository, session, runtime, emailPassword, appsScript, hooks, }) {
16
16
  const sessionStorage = sessionStorageFactory(session.storageType, {
17
17
  cacheService: runtime.cacheService,
18
18
  propertiesService: runtime.propertiesService,
@@ -32,6 +32,11 @@ export class AppsScriptAuth {
32
32
  utilities: runtime.utilities,
33
33
  session: runtime.session,
34
34
  expiresIn,
35
+ ...(hooks?.afterSignIn
36
+ ? {
37
+ afterSignIn: hooks.afterSignIn,
38
+ }
39
+ : {}),
35
40
  });
36
41
  this.signUp = new AppsScriptAuthSignUp({
37
42
  sessionStorage,
@@ -2,13 +2,14 @@ import type { AppsScriptAuthenticationConfig } from "../authentication/AppsScrip
2
2
  import { type AppsScriptCredential } from "../authentication/AppsScriptAuthentication";
3
3
  import type { EmailPasswordAuthConfig } from "../authentication/EmailPasswordAuthentication";
4
4
  import { type EmailPasswordCredential } from "../authentication/EmailPasswordAuthentication";
5
+ import type { AfterSignInHook } from "../hooks/AuthHooks";
5
6
  import { AppsScriptAuthRepository } from "../storage/AppsScriptAuthRepository";
6
7
  import { AppsScriptSessionStorage } from "../storage/AppsScriptSessionStorage";
7
8
  import { SignIn } from "./SignIn";
8
- export declare class AppsScriptAuthSignIn {
9
- email: SignIn<EmailPasswordCredential>["execute"];
10
- appsScript: SignIn<AppsScriptCredential>["execute"];
11
- constructor({ sessionStorage, expiresIn, repository, emailPassword, appsScript, session, utilities, }: {
9
+ export declare class AppsScriptAuthSignIn<THookResult = undefined> {
10
+ email: SignIn<EmailPasswordCredential, THookResult>["execute"];
11
+ appsScript: SignIn<AppsScriptCredential, THookResult>["execute"];
12
+ constructor({ sessionStorage, expiresIn, repository, emailPassword, appsScript, session, utilities, afterSignIn, }: {
12
13
  sessionStorage: AppsScriptSessionStorage;
13
14
  repository: AppsScriptAuthRepository;
14
15
  emailPassword?: EmailPasswordAuthConfig;
@@ -16,5 +17,6 @@ export declare class AppsScriptAuthSignIn {
16
17
  utilities: GoogleAppsScript.Utilities.Utilities;
17
18
  session: GoogleAppsScript.Base.Session;
18
19
  expiresIn: number;
20
+ afterSignIn?: AfterSignInHook<THookResult>;
19
21
  });
20
22
  }
@@ -4,7 +4,7 @@ import { SignIn } from "./SignIn";
4
4
  export class AppsScriptAuthSignIn {
5
5
  email;
6
6
  appsScript;
7
- constructor({ sessionStorage, expiresIn, repository, emailPassword, appsScript, session, utilities, }) {
7
+ constructor({ sessionStorage, expiresIn, repository, emailPassword, appsScript, session, utilities, afterSignIn, }) {
8
8
  this.email = async (credential) => {
9
9
  if (!emailPassword) {
10
10
  throw new Error("Email and password authentication is disabled");
@@ -15,6 +15,11 @@ export class AppsScriptAuthSignIn {
15
15
  authentication: new EmailPasswordAuthentication(repository, utilities, emailPassword),
16
16
  utilities,
17
17
  expiresIn,
18
+ ...(afterSignIn
19
+ ? {
20
+ afterSignIn,
21
+ }
22
+ : {}),
18
23
  });
19
24
  return emailSignIn.execute(credential);
20
25
  };
@@ -28,6 +33,11 @@ export class AppsScriptAuthSignIn {
28
33
  authentication: new AppsScriptAuthentication(repository, session),
29
34
  utilities,
30
35
  expiresIn,
36
+ ...(afterSignIn
37
+ ? {
38
+ afterSignIn,
39
+ }
40
+ : {}),
31
41
  }).execute(credential);
32
42
  };
33
43
  }
@@ -1,19 +1,30 @@
1
1
  import { Authentication } from "../authentication/Authentication";
2
2
  import { Session } from "../domain/Session";
3
+ import type { User } from "../domain/User";
4
+ import type { AfterSignInHook } from "../hooks/AuthHooks";
3
5
  import type { AppsScriptSessionStorage } from "../storage/AppsScriptSessionStorage";
4
- export declare class SignIn<T> {
6
+ export type SignInResult<THookResult = undefined> = [THookResult] extends [
7
+ undefined
8
+ ] ? {
9
+ readonly user: User;
10
+ readonly session: Session;
11
+ } : {
12
+ readonly user: User;
13
+ readonly session: Session;
14
+ readonly hooks: THookResult;
15
+ };
16
+ export declare class SignIn<TCredential, THookResult = undefined> {
5
17
  private readonly sessionStorage;
6
18
  private readonly expiresIn;
7
19
  private readonly utilities;
8
20
  private readonly authentication;
9
- constructor({ sessionStorage, authentication, utilities, expiresIn, }: {
21
+ private readonly afterSignIn;
22
+ constructor({ sessionStorage, authentication, utilities, expiresIn, afterSignIn, }: {
10
23
  sessionStorage: AppsScriptSessionStorage;
11
- authentication: Authentication<T>;
24
+ authentication: Authentication<TCredential>;
12
25
  utilities: GoogleAppsScript.Utilities.Utilities;
13
26
  expiresIn: number;
27
+ afterSignIn?: AfterSignInHook<THookResult>;
14
28
  });
15
- execute(credential: T): Promise<{
16
- user: import("..").User;
17
- session: Session;
18
- }>;
29
+ execute(credential: TCredential): Promise<SignInResult<THookResult>>;
19
30
  }
@@ -4,15 +4,15 @@ export class SignIn {
4
4
  expiresIn;
5
5
  utilities;
6
6
  authentication;
7
- constructor({ sessionStorage, authentication, utilities, expiresIn, }) {
8
- // Initialize SignIn with db, schema, and session
7
+ afterSignIn;
8
+ constructor({ sessionStorage, authentication, utilities, expiresIn, afterSignIn, }) {
9
9
  this.sessionStorage = sessionStorage;
10
10
  this.authentication = authentication;
11
11
  this.utilities = utilities;
12
12
  this.expiresIn = expiresIn;
13
+ this.afterSignIn = afterSignIn;
13
14
  }
14
15
  async execute(credential) {
15
- // Implement the sign-in logic using the provided authentication method
16
16
  const user = await this.authentication.verify(credential);
17
17
  const session = new Session({
18
18
  id: this.utilities.getUuid(),
@@ -21,6 +21,32 @@ export class SignIn {
21
21
  expiresAt: new Date(Date.now() + this.expiresIn),
22
22
  });
23
23
  await this.sessionStorage.save(session);
24
- return { user, session };
24
+ if (!this.afterSignIn) {
25
+ return {
26
+ user,
27
+ session,
28
+ };
29
+ }
30
+ const context = {
31
+ user,
32
+ session,
33
+ };
34
+ try {
35
+ const hooks = await this.afterSignIn(context);
36
+ return {
37
+ user,
38
+ session,
39
+ hooks,
40
+ };
41
+ }
42
+ catch (error) {
43
+ try {
44
+ await this.sessionStorage.delete(session.id);
45
+ }
46
+ catch {
47
+ // Preserve the original hook error.
48
+ }
49
+ throw error;
50
+ }
25
51
  }
26
52
  }
@@ -0,0 +1,10 @@
1
+ import type { Session } from "../domain/Session";
2
+ import type { User } from "../domain/User";
3
+ export type AfterSignInContext = {
4
+ readonly user: User;
5
+ readonly session: Session;
6
+ };
7
+ export type AfterSignInHook<TResult> = (context: AfterSignInContext) => TResult | Promise<TResult>;
8
+ export type AuthHooks<TResult = undefined> = {
9
+ readonly afterSignIn?: AfterSignInHook<TResult>;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export { PasswordCredential } from "./domain/PasswordCredential";
9
9
  export { PasswordReset } from "./domain/PasswordReset";
10
10
  export { AppsScriptIdentity } from "./identity/AppsScriptIdentity";
11
11
  export { EmailPasswordIdentity } from "./identity/EmailPasswordIdentity";
12
+ export type { AfterSignInContext, AfterSignInHook, AuthHooks, } from "./hooks/AuthHooks";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gasboost/auth",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Modern authentication for Google Apps Script with email/password, Apps Script authentication, sessions, and password reset.",
5
5
  "keywords": [
6
6
  "google-apps-script",