import * as validators from "express-validator"; import { App } from "../App"; import { Context } from "../Context"; import * as fw from "../fw"; import * as resolvers from "../resolvers"; import * as stores from "../stores"; import * as types from "../types"; import BaseController from "./BaseController"; interface RegisterUserParams { id: string; idToken: string; name: string; } interface UpdateUserParams { id: string; idToken: string; name: string; } interface AuthenticateParams { id: string; idToken: string; } export default class extends BaseController { constructor(app: App) { super(app); this.validators.post = [ fw.params.InstantValidator( [ validators.body("id").isString().notEmpty(), // TODO: validate format validators.body("idToken").isString().notEmpty(), validators.body("name").isString().notEmpty().isLength({ min: this.app.userConfig.acceptNameLength.min, max: this.app.userConfig.acceptNameLength.max, }), ], (context) => ({ id: context.req.body.id, idToken: context.req.body.idToken, name: context.req.body.name, } as RegisterUserParams) ), ]; this.validators.put = [ fw.params.InstantValidator( [ validators.param("id").isString().notEmpty(), // TODO: validate format validators.body("idToken").isString().notEmpty(), validators.body("name").isString().notEmpty().isLength({ min: this.app.userConfig.acceptNameLength.min, max: this.app.userConfig.acceptNameLength.max, }), ], (context) => ({ id: context.req.params.id, idToken: context.req.body.idToken, name: context.req.body.name, } as UpdateUserParams) ), ]; } register(basePath: string) { const router = super.register(basePath); this.registerRoute(router, "POST", "/:id/authenticate", this.authenticate, [ fw.params.InstantValidator( [ validators.param("id").isString().notEmpty(), // TODO: validate format validators.body("idToken").isString().notEmpty(), ], (context) => ({ id: context.req.params.id, idToken: context.req.body.idToken, } as AuthenticateParams) ), ]); return router; } async authenticate(context: Context) { const p = context.params as AuthenticateParams; const verifyResult = await this.app.auth.verifyIdToken(p.idToken); if (p.id !== verifyResult.uid) { throw new fw.types.Forbidden("Invalid user id"); } const user = await resolvers.Users.resolve(this.app.firestore, p.id); // Note: userは毎回作っていないので、いないのもOKとする const customClaims = { admin: user?.admin, name: user?.name, }; if (verifyResult["admin"] !== customClaims.admin || verifyResult["name"] !== customClaims.name) { this.app.auth.setCustomUserClaims(verifyResult.uid, customClaims); } return customClaims; } async post(context: Context) { const p = context.params as RegisterUserParams; const verifyResult = await this.app.auth.verifyIdToken(p.idToken); if (p.id !== verifyResult.uid) { throw new fw.types.Forbidden("Invalid user id"); } const user = await resolvers.Users.resolve(this.app.firestore, p.id); if (user != null) { throw new types.DuplicateUserError(); } await stores.storeUser(this.app.firestore, p.id, p.name); const customClaims = { name: p.name, }; console.log("customClaims", customClaims); this.app.auth.setCustomUserClaims(verifyResult.uid, customClaims); return customClaims; } async put(context: Context) { const p = context.params as UpdateUserParams; const verifyResult = await this.app.auth.verifyIdToken(p.idToken); if (p.id !== verifyResult.uid) { throw new fw.types.Forbidden("Invalid user id"); } const user = await resolvers.Users.resolve(this.app.firestore, p.id); if (user == null) { throw new fw.types.NotFound("User not found"); } await stores.updateUser(this.app.firestore, p.id, p.name); const customClaims = { name: p.name, }; this.app.auth.setCustomUserClaims(verifyResult.uid, customClaims); return customClaims; } }