@gasboost/auth-app 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -88,7 +88,7 @@ session storage や repository を middleware 側で再定義する必要はあ
88
88
  ```ts
89
89
  import { AppsScript } from "@gasboost/app";
90
90
  import { AppsScriptAuth } from "@gasboost/auth";
91
- import { authentication, type AuthState } from "@gasboost/auth-app";
91
+ import { authentication } from "@gasboost/auth-app";
92
92
 
93
93
  const auth = new AppsScriptAuth({
94
94
  repository,
@@ -105,7 +105,7 @@ const auth = new AppsScriptAuth({
105
105
  },
106
106
  });
107
107
 
108
- const app = new AppsScript<AuthState>().use(authentication(auth));
108
+ const app = new AppsScript().use(authentication(auth));
109
109
  ```
110
110
 
111
111
  `authentication()` は `AppsScriptAuth` の既存 session API を利用します。
@@ -114,7 +114,64 @@ const app = new AppsScript<AuthState>().use(authentication(auth));
114
114
  const session = await auth.session.get(token);
115
115
  ```
116
116
 
117
- session の取得だけを目的とした別の authentication API は使用しません。
117
+ 有効な session が取得できた場合、middleware はその session を `AppsScript` state に設定します。
118
+
119
+ `@gasboost/app` v4 では `authentication()` の適用後、後続 handler の `context.state` から session が non-nullable として取得できます。
120
+
121
+ ## Auth handlers
122
+
123
+ `handlers(auth)` は `AppsScriptAuth` の公開 API を `@gasboost/app` の RPC handler として登録できる形へ変換します。
124
+
125
+ ```ts
126
+ import { AppsScript } from "@gasboost/app";
127
+ import { AppsScriptAuth } from "@gasboost/auth";
128
+ import { authentication, handlers } from "@gasboost/auth-app";
129
+
130
+ const auth = new AppsScriptAuth({
131
+ repository,
132
+ runtime,
133
+ session: {
134
+ storageType: "cache",
135
+ },
136
+ });
137
+
138
+ const app = new AppsScript().use(authentication(auth)).calls(handlers(auth));
139
+ ```
140
+
141
+ 以下の RPC が公開されます。
142
+
143
+ - `signInEmail`
144
+ - `signInAppsScript`
145
+ - `signUpEmail`
146
+ - `signUpAppsScript`
147
+ - `getSession`
148
+ - `signOut`
149
+
150
+ Password Reset が有効な場合は、さらに以下も公開されます。
151
+
152
+ - `forgotPassword`
153
+ - `resetPassword`
154
+
155
+ `signIn` / `signUp` / Password Reset の input と result の型は `@gasboost/auth` の既存 API から推論されます。
156
+
157
+ `getSession` と `signOut` は、RPC の単一 object input contract に合わせて以下の形式になります。
158
+
159
+ ```ts
160
+ client.getSession({
161
+ sessionId: "session-id",
162
+ });
163
+
164
+ client.signOut({
165
+ sessionId: "session-id",
166
+ });
167
+ ```
168
+
169
+ `@gasboost/auth` 側の API 自体は変更されません。
170
+
171
+ ```ts
172
+ await auth.session.get("session-id");
173
+ await auth.signOut.execute("session-id");
174
+ ```
118
175
 
119
176
  ## Authenticated RPC
120
177
 
@@ -122,25 +179,19 @@ session の取得だけを目的とした別の authentication API は使用し
122
179
 
123
180
  ```ts
124
181
  import { AppsScript } from "@gasboost/app";
125
- import {
126
- authentication,
127
- type AuthenticatedInput,
128
- type AuthState,
129
- } from "@gasboost/auth-app";
182
+ import { authentication, type AuthenticatedInput } from "@gasboost/auth-app";
130
183
 
131
- const app = new AppsScript<AuthState>()
184
+ const app = new AppsScript()
132
185
  .use(authentication(auth))
133
- .call("getProfile", (_input: AuthenticatedInput) => {
134
- const session = app.state.get("session");
135
-
136
- if (!session) {
137
- throw new Error("Unauthorized");
138
- }
186
+ .call("getProfile", (_input: AuthenticatedInput, context) => {
187
+ const session = context.state.get("session");
139
188
 
140
189
  return getProfile(session.userId);
141
190
  });
142
191
  ```
143
192
 
193
+ `authentication(auth)` の通過後は session が state に存在することが型として保証されるため、`undefined` check は不要です。
194
+
144
195
  クライアント側では token が必須になります。
145
196
 
146
197
  ```ts
@@ -149,23 +200,18 @@ client.getProfile({
149
200
  });
150
201
  ```
151
202
 
152
- token を渡さない呼び出しは RPC contract 上エラーになります。
153
-
154
203
  追加 input が必要な場合:
155
204
 
156
205
  ```ts
157
- const app = new AppsScript<AuthState>().use(authentication(auth)).call(
206
+ const app = new AppsScript().use(authentication(auth)).call(
158
207
  "updateProfile",
159
208
  (
160
209
  input: AuthenticatedInput<{
161
210
  name: string;
162
211
  }>,
212
+ context,
163
213
  ) => {
164
- const session = app.state.get("session");
165
-
166
- if (!session) {
167
- throw new Error("Unauthorized");
168
- }
214
+ const session = context.state.get("session");
169
215
 
170
216
  return updateProfile({
171
217
  userId: session.userId,
@@ -191,7 +237,7 @@ middleware はすべての RPC に認証を要求するわけではありませ
191
237
  token を持たない RPC input はそのまま後続 handler へ流れます。
192
238
 
193
239
  ```ts
194
- const app = new AppsScript<AuthState>()
240
+ const app = new AppsScript()
195
241
  .use(authentication(auth))
196
242
  .call("signIn", (input: { email: string; password: string }) => {
197
243
  return auth.signIn.email(input);
@@ -210,13 +256,11 @@ client.signIn({
210
256
  input 自体を持たない公開 RPC も利用できます。
211
257
 
212
258
  ```ts
213
- const app = new AppsScript<AuthState>()
214
- .use(authentication(auth))
215
- .call("health", () => {
216
- return {
217
- ok: true,
218
- };
219
- });
259
+ const app = new AppsScript().use(authentication(auth)).call("health", () => {
260
+ return {
261
+ ok: true,
262
+ };
263
+ });
220
264
  ```
221
265
 
222
266
  ## Session state
@@ -227,31 +271,36 @@ const app = new AppsScript<AuthState>()
227
271
  context.state.set("session", session);
228
272
  ```
229
273
 
230
- application handler からは次のように取得できます。
274
+ `authentication(auth)` より後の application handler では、`context.state` から取得できます。
231
275
 
232
276
  ```ts
233
- const session = app.state.get("session");
277
+ const app = new AppsScript()
278
+ .use(authentication(auth))
279
+ .call("getProfile", (_input: AuthenticatedInput, context) => {
280
+ const session = context.state.get("session");
234
281
 
235
- if (!session) {
236
- throw new Error("Unauthorized");
237
- }
282
+ session.id;
283
+ session.userId;
284
+ session.createdAt;
285
+ session.expiresAt;
238
286
 
239
- session.id;
240
- session.userId;
241
- session.createdAt;
242
- session.expiresAt;
287
+ return getProfile(session.userId);
288
+ });
243
289
  ```
244
290
 
291
+ `authentication(auth)` が session state を保証するため、後続 handler では `session` は `undefined` になりません。
292
+
245
293
  application user が必要な場合は `session.userId` を利用して application 側で取得します。
246
294
 
247
295
  ```ts
248
- const session = app.state.get("session");
249
-
250
- if (!session) {
251
- throw new Error("Unauthorized");
252
- }
296
+ const app = new AppsScript()
297
+ .use(authentication(auth))
298
+ .call("getProfile", async (_input: AuthenticatedInput, context) => {
299
+ const session = context.state.get("session");
300
+ const user = await userRepository.find(session.userId);
253
301
 
254
- const user = await userRepository.find(session.userId);
302
+ return user;
303
+ });
255
304
  ```
256
305
 
257
306
  `@gasboost/auth-app` は application User の取得までは担当しません。
@@ -298,18 +347,32 @@ Apps Script Active User を application session の代替として扱いませ
298
347
 
299
348
  ## Custom state
300
349
 
301
- application 独自の state と組み合わせる場合は `AuthState` と intersection できます。
350
+ ## Custom state
351
+
352
+ `@gasboost/app` v4 では middleware が追加する state は `.use()` によって型へ反映されます。
302
353
 
303
354
  ```ts
304
- import { authentication, type AuthState } from "@gasboost/auth-app";
355
+ const app = new AppsScript().use(authentication(auth));
356
+ ```
305
357
 
306
- type AppState = AuthState & {
307
- requestId: string;
308
- };
358
+ `authentication(auth)` より後の handler では session state が保証されます。
359
+
360
+ ```ts
361
+ const app = new AppsScript()
362
+ .calls(handlers(auth))
363
+ .call("health", () => ({
364
+ ok: true,
365
+ }))
366
+ .use(authentication(auth))
367
+ .call("getProfile", (_input: AuthenticatedInput, context) => {
368
+ const session = context.state.get("session");
309
369
 
310
- const app = new AppsScript<AppState>().use(authentication(auth));
370
+ return getProfile(session.userId);
371
+ });
311
372
  ```
312
373
 
374
+ application 独自の state を追加する場合も、対応する middleware を `.use()` で組み合わせます。
375
+
313
376
  ## Responsibility
314
377
 
315
378
  `@gasboost/auth-app` が担当するもの:
@@ -1,4 +1,4 @@
1
1
  import type { AppsScriptMiddleware } from "@gasboost/app";
2
2
  import type { AppsScriptAuth } from "@gasboost/auth";
3
3
  import type { AuthState } from "./AuthState";
4
- export declare function authentication(auth: Pick<AppsScriptAuth, "session">): AppsScriptMiddleware<AuthState>;
4
+ export declare function authentication(auth: Pick<AppsScriptAuth, "session">): AppsScriptMiddleware<Record<never, never>, AuthState>;
@@ -7,14 +7,13 @@ function authentication(auth) {
7
7
  return next();
8
8
  }
9
9
  const input = context.invocation.input;
10
- if (typeof input !== "object" || input === null || !("token" in input)) {
11
- return next();
12
- }
13
- const token = input.token;
14
- if (typeof token !== "string") {
10
+ if (typeof input !== "object" ||
11
+ input === null ||
12
+ !("token" in input) ||
13
+ typeof input.token !== "string") {
15
14
  throw new Error("Unauthorized");
16
15
  }
17
- const session = await auth.session.get(token);
16
+ const session = await auth.session.get(input.token);
18
17
  if (session === null) {
19
18
  throw new Error("Unauthorized");
20
19
  }
@@ -0,0 +1,28 @@
1
+ import type { AppsScriptAuth } from "@gasboost/auth";
2
+ type EmailPasswordOptions = ConstructorParameters<typeof AppsScriptAuth>[0]["emailPassword"];
3
+ type EnabledEmailPasswordOptions = NonNullable<EmailPasswordOptions> & {
4
+ passwordReset: NonNullable<NonNullable<EmailPasswordOptions>["passwordReset"]>;
5
+ };
6
+ type PasswordApi = NonNullable<AppsScriptAuth<EnabledEmailPasswordOptions>["password"]>;
7
+ type Auth<TEmailPassword extends EmailPasswordOptions, THookResult> = AppsScriptAuth<TEmailPassword, THookResult>;
8
+ type BaseHandlers<TEmailPassword extends EmailPasswordOptions, THookResult> = {
9
+ signInEmail: Auth<TEmailPassword, THookResult>["signIn"]["email"];
10
+ signInAppsScript: Auth<TEmailPassword, THookResult>["signIn"]["appsScript"];
11
+ signUpEmail: Auth<TEmailPassword, THookResult>["signUp"]["email"];
12
+ signUpAppsScript: Auth<TEmailPassword, THookResult>["signUp"]["appsScript"];
13
+ getSession: (input: {
14
+ sessionId: Parameters<Auth<TEmailPassword, THookResult>["session"]["get"]>[0];
15
+ }) => ReturnType<Auth<TEmailPassword, THookResult>["session"]["get"]>;
16
+ signOut: (input: {
17
+ sessionId: Parameters<Auth<TEmailPassword, THookResult>["signOut"]["execute"]>[0];
18
+ }) => ReturnType<Auth<TEmailPassword, THookResult>["signOut"]["execute"]>;
19
+ };
20
+ type PasswordHandlers = {
21
+ forgotPassword: PasswordApi["forgot"];
22
+ resetPassword: PasswordApi["reset"];
23
+ };
24
+ export type AuthHandlers<TEmailPassword extends EmailPasswordOptions, THookResult> = BaseHandlers<TEmailPassword, THookResult> & (TEmailPassword extends {
25
+ passwordReset: unknown;
26
+ } ? PasswordHandlers : Record<never, never>);
27
+ export declare function handlers<TEmailPassword extends EmailPasswordOptions, THookResult>(auth: AppsScriptAuth<TEmailPassword, THookResult>): AuthHandlers<TEmailPassword, THookResult>;
28
+ export {};
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handlers = handlers;
4
+ function handlers(auth) {
5
+ const baseHandlers = {
6
+ signInEmail: auth.signIn.email,
7
+ signInAppsScript: auth.signIn.appsScript,
8
+ signUpEmail: auth.signUp.email,
9
+ signUpAppsScript: auth.signUp.appsScript,
10
+ getSession: ({ sessionId }) => auth.session.get(sessionId),
11
+ signOut: ({ sessionId }) => auth.signOut.execute(sessionId),
12
+ };
13
+ const password = auth.password;
14
+ if (password === undefined) {
15
+ return baseHandlers;
16
+ }
17
+ return {
18
+ ...baseHandlers,
19
+ forgotPassword: password.forgot.bind(password),
20
+ resetPassword: password.reset.bind(password),
21
+ };
22
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export type { AuthenticatedInput } from "./AuthenticatedInput";
2
2
  export { authentication } from "./authentication";
3
3
  export type { AuthSession, AuthState } from "./AuthState";
4
+ export { handlers } from "./handlers";
5
+ export type { AuthHandlers } from "./handlers";
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.authentication = void 0;
3
+ exports.handlers = exports.authentication = void 0;
4
4
  var authentication_1 = require("./authentication");
5
5
  Object.defineProperty(exports, "authentication", { enumerable: true, get: function () { return authentication_1.authentication; } });
6
+ var handlers_1 = require("./handlers");
7
+ Object.defineProperty(exports, "handlers", { enumerable: true, get: function () { return handlers_1.handlers; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gasboost/auth-app",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Authentication middleware adapter for @gasboost/auth and @gasboost/app.",
5
5
  "keywords": [
6
6
  "google-apps-script",
@@ -13,6 +13,7 @@
13
13
  "gasboost"
14
14
  ],
15
15
  "license": "MIT",
16
+ "type": "commonjs",
16
17
  "main": "./dist/index.js",
17
18
  "types": "./dist/index.d.ts",
18
19
  "files": [
@@ -38,13 +39,13 @@
38
39
  "access": "public"
39
40
  },
40
41
  "peerDependencies": {
41
- "@gasboost/app": "^3.0.0",
42
- "@gasboost/auth": "^0.2.0"
42
+ "@gasboost/app": "^5.0.0",
43
+ "@gasboost/auth": "^0.4.0"
43
44
  },
44
45
  "devDependencies": {
45
- "@gasboost/app": "^3.0.0",
46
+ "@gasboost/app": "^5.0.0",
46
47
  "@types/google-apps-script": "^2.0.13",
47
- "@gasboost/auth": "0.2.1"
48
+ "@gasboost/auth": "0.4.1"
48
49
  },
49
50
  "scripts": {
50
51
  "format": "prettier --check . --config ../../.prettierrc.json --ignore-path ../../.prettierignore",