@gasboost/auth-app
v0.3.0
Published
Authentication middleware adapter for @gasboost/auth and @gasboost/app.
Maintainers
Readme
@gasboost/auth-app
@gasboost/auth と @gasboost/app を接続する authentication middleware adapter です。
認証必須 RPC の input に session token を含め、RPC 実行前に既存の auth.session.get(token) を利用して session を検証します。
有効な session は AppsScript の state に保存されます。
RPC input
↓
token
↓
@gasboost/auth-app
↓
auth.session.get(token)
↓
Session
↓
AppsScript state@gasboost/app 自体は @gasboost/auth を認識しません。
@gasboost/auth
↑
@gasboost/auth-app → @gasboost/appInstall
pnpm add @gasboost/auth-app @gasboost/auth @gasboost/appnpm:
npm install @gasboost/auth-app @gasboost/auth @gasboost/appAuthenticatedInput
認証が必要な RPC では AuthenticatedInput を handler input に指定します。
import type { AuthenticatedInput } from "@gasboost/auth-app";token だけ必要な場合:
type Input = AuthenticatedInput;これは以下の型になります。
type Input = {
token: string;
};追加の input がある場合は generic parameter に object を指定します。
type Input = AuthenticatedInput<{
name: string;
}>;これは以下の型になります。
type Input = {
name: string;
token: string;
};Authentication middleware
既存の AppsScriptAuth instance を authentication() に渡します。
session storage や repository を middleware 側で再定義する必要はありません。
import { AppsScript } from "@gasboost/app";
import { AppsScriptAuth } from "@gasboost/auth";
import { authentication } from "@gasboost/auth-app";
const auth = new AppsScriptAuth({
repository,
runtime: {
utilities: Utilities,
session: Session,
cacheService: CacheService,
propertiesService: PropertiesService,
},
session: {
storageType: "cache",
},
});
const app = new AppsScript().use(authentication(auth));authentication() は AppsScriptAuth の既存 session API を利用します。
const session = await auth.session.get(token);有効な session が取得できた場合、middleware はその session を AppsScript state に設定します。
@gasboost/app v4 では authentication() の適用後、後続 handler の context.state から session が non-nullable として取得できます。
Auth handlers
handlers(auth) は AppsScriptAuth の公開 API を @gasboost/app の RPC handler として登録できる形へ変換します。
import { AppsScript } from "@gasboost/app";
import { AppsScriptAuth } from "@gasboost/auth";
import { authentication, handlers } from "@gasboost/auth-app";
const auth = new AppsScriptAuth({
repository,
runtime,
session: {
storageType: "cache",
},
});
const app = new AppsScript().use(authentication(auth)).calls(handlers(auth));以下の RPC が公開されます。
signInEmailsignInAppsScriptsignUpEmailsignUpAppsScriptgetSessionsignOut
Password Reset が有効な場合は、さらに以下も公開されます。
forgotPasswordresetPassword
signIn / signUp / Password Reset の input と result の型は @gasboost/auth の既存 API から推論されます。
getSession と signOut は、RPC の単一 object input contract に合わせて以下の形式になります。
client.getSession({
sessionId: "session-id",
});
client.signOut({
sessionId: "session-id",
});@gasboost/auth 側の API 自体は変更されません。
await auth.session.get("session-id");
await auth.signOut.execute("session-id");Authorization Middleware
authorization() は、authentication(auth) が state に保存した session.userId を使って AppsScriptAuthorization.can() に委譲します。token の再検証、Session Storage へのアクセス、Repository への直接アクセスは行いません。
import {
authentication,
authorization as requireAuthorization,
} from "@gasboost/auth-app";
const app = new AppsScript()
.use(authentication(auth))
.use(
requireAuthorization(authorization, {
project: ["update"],
}),
)
.call("updateProject", updateProject);許可されない場合は Forbidden を throw します。認証に失敗した場合の Unauthorized とは区別されます。
authorizationHandlers(authorization) は role / permission assignment 用の handler を生成します。
const handlers = authorizationHandlers(authorization);
app.call("assignRole", handlers.role.assign);
app.call("allowPermission", handlers.permission.allow);これらの handler は強い権限を持つ master operation です。自動登録や暗黙の管理権限付与は行わないため、公開する RPC 名と保護用 middleware は application 側で明示してください。
Authenticated RPC
認証が必要な RPC は AuthenticatedInput を利用します。
import { AppsScript } from "@gasboost/app";
import { authentication, type AuthenticatedInput } from "@gasboost/auth-app";
const app = new AppsScript()
.use(authentication(auth))
.call("getProfile", (_input: AuthenticatedInput, context) => {
const session = context.state.get("session");
return getProfile(session.userId);
});authentication(auth) の通過後は session が state に存在することが型として保証されるため、undefined check は不要です。
クライアント側では token が必須になります。
client.getProfile({
token,
});追加 input が必要な場合:
const app = new AppsScript().use(authentication(auth)).call(
"updateProfile",
(
input: AuthenticatedInput<{
name: string;
}>,
context,
) => {
const session = context.state.get("session");
return updateProfile({
userId: session.userId,
name: input.name,
});
},
);クライアント:
client.updateProfile({
token,
name: "Taro",
});Public RPC
middleware はすべての RPC に認証を要求するわけではありません。
token を持たない RPC input はそのまま後続 handler へ流れます。
const app = new AppsScript()
.use(authentication(auth))
.call("signIn", (input: { email: string; password: string }) => {
return auth.signIn.email(input);
});クライアント側でも token は不要です。
client.signIn({
email: "[email protected]",
password: "password",
});input 自体を持たない公開 RPC も利用できます。
const app = new AppsScript().use(authentication(auth)).call("health", () => {
return {
ok: true,
};
});Session state
認証に成功すると middleware は取得した session を AppsScript state に設定します。
context.state.set("session", session);authentication(auth) より後の application handler では、context.state から取得できます。
const app = new AppsScript()
.use(authentication(auth))
.call("getProfile", (_input: AuthenticatedInput, context) => {
const session = context.state.get("session");
session.id;
session.userId;
session.createdAt;
session.expiresAt;
return getProfile(session.userId);
});authentication(auth) が session state を保証するため、後続 handler では session は undefined になりません。
application user が必要な場合は session.userId を利用して application 側で取得します。
const app = new AppsScript()
.use(authentication(auth))
.call("getProfile", async (_input: AuthenticatedInput, context) => {
const session = context.state.get("session");
const user = await userRepository.find(session.userId);
return user;
});@gasboost/auth-app は application User の取得までは担当しません。
Unauthorized
token プロパティが存在する場合、middleware は token を検証します。
以下の場合は Unauthorized error になります。
tokenが string ではないauth.session.get(token)が session を返さない- session が期限切れ
throw new Error("Unauthorized");期限切れ session の判定と削除は @gasboost/auth の既存 session.get() が担当します。
middleware 側で session storage を直接操作することはありません。
Apps Script identity
Session.getActiveUser().getEmail() と application session は別の概念です。
Session.getActiveUser().getEmail()
≠
application sessionこの middleware では application の認証状態を RPC input の session token から確認します。
token
↓
auth.session.get(token)
↓
application SessionApps Script Active User を application session の代替として扱いません。
Custom state
Custom state
@gasboost/app v4 では middleware が追加する state は .use() によって型へ反映されます。
const app = new AppsScript().use(authentication(auth));authentication(auth) より後の handler では session state が保証されます。
const app = new AppsScript()
.calls(handlers(auth))
.call("health", () => ({
ok: true,
}))
.use(authentication(auth))
.call("getProfile", (_input: AuthenticatedInput, context) => {
const session = context.state.get("session");
return getProfile(session.userId);
});application 独自の state を追加する場合も、対応する middleware を .use() で組み合わせます。
Responsibility
@gasboost/auth-app が担当するもの:
- authenticated RPC input の token contract
- RPC input から token の取得
auth.session.get(token)による session 検証- session の AppsScript state への設定
- invalid token の拒否
担当しないもの:
- session storage の構築
- repository の構築
- User の取得
- authorization
Session.getActiveUser()による identity 解決
これらはそれぞれ @gasboost/auth または application の責務です。
