diff --git a/src/config.ts b/src/config.ts index db2a370..e409431 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,8 +1,4 @@ export const Config = { - sms_api_key: "1796570121771765", - sms_api_secret: "0957b611d575febff1ae0fc51070c8b7", - sms_api_request_endpoint: "https://otp.thaibulksms.com/v2/otp/request", - sms_api_verify_endpoint: "https://otp.thaibulksms.com/v2/otp/verify", jwt_secret: "T4kE6/tIqCVEZYg9lwsqeJjYfOoXTXSXDEMyParsJjj57CjSdkrfPOLWP74/9lJpcBA=", token_duration: "365d", diff --git a/src/userRoute.ts b/src/userRoute.ts index 13fb0fa..d3d94bf 100644 --- a/src/userRoute.ts +++ b/src/userRoute.ts @@ -14,8 +14,13 @@ import { TRPCError } from "@trpc/server"; import * as jwt from "jsonwebtoken"; const userInsertSchema = createInsertSchema(user, { - cid: (schema) => schema.cid.length(13), + cid: (schema) => + schema.cid.length(13).refine(isValidThaiID, { message: "Invalid Thai ID" }), }); + +const userUpdateSchema = userInsertSchema + .omit({ id: true, cid: true, phone: true }) + .partial(); const opinionInsertSchema = createInsertSchema(userOpinion) .omit({ userId: true, @@ -30,6 +35,7 @@ const opinionUpdateSchema = createInsertSchema(userOpinion) .required({ opinionId: true }); type OpinionInsertSchema = z.infer; type UserInsertSchema = z.infer; +type UserUpdateSchema = z.infer; export const userRoute = router({ getAllUser: publicProcedure @@ -45,27 +51,21 @@ export const userRoute = router({ async ({ input }) => await getAllUser(input.offset, input.limit, input.group, input.zone) ), - createUser: verifiedPhone + createUser: publicProcedure .input( - userInsertSchema.omit({ id: true, phone: true }).extend({ + userInsertSchema.omit({ id: true }).extend({ opinions: opinionInsertSchema, }) ) .mutation( - async ({ input, ctx }) => - await createUser({ ...input, phone: ctx.phone }, input.opinions) + async ({ input }) => await createUser({ ...input }, input.opinions) ), - requestOtp: publicProcedure - .input(z.object({ phone: z.string().trim().min(5) })) - .mutation(async ({ input }) => await requestOtp(input.phone)), - verifyOtp: publicProcedure - .input( - z.object({ - pin: z.string().trim(), - token: z.string(), - }) - ) - .mutation(async ({ input }) => await verifyOtp(input.token, input.pin)), + updateUser: protectedProcedure + .input(userUpdateSchema) + .mutation(async ({ input, ctx }) => await updateUser(ctx.user.id, input)), + login: publicProcedure + .input(z.object({ cid: z.string(), phone: z.string() })) + .mutation(async ({ input }) => await login(input.cid, input.phone)), changeOpinion: protectedProcedure .input(opinionUpdateSchema) .mutation( @@ -114,97 +114,43 @@ async function createUser( for (let op of opinions) { await db.insert(userOpinion).values({ ...op, userId: result.id }); } - return { status: "OK" }; + return { token: createJWT(newUser.phone) }; } catch (e) { console.error(e); - throw new Error(`Unable to create new user:\n${e}`); + throw new Error(`Unable to create new user`); } } -async function requestOtp(phone: string) { - const _phone = phone.trim(); +async function updateUser(userId: number, update: UserUpdateSchema) { try { - let rs = await fetch(Config.sms_api_request_endpoint, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - key: Config.sms_api_key, - secret: Config.sms_api_secret, - msisdn: _phone, - }), - }).then((res) => res.json()); - - if (rs.errors) { - console.error(rs); - throw new TRPCError({ - message: `Unable to request OTP`, - code: "INTERNAL_SERVER_ERROR", - }); - } - await db.insert(phoneToken).values({ phone: _phone, token: rs.token }); - return { - status: rs.status as string, - token: rs.token as string, - refno: rs.refno as string, - }; + await db.update(user).set(update).where(eq(user.id, userId)); + return { status: "success" }; } catch (e) { console.error(e); - throw new TRPCError({ - message: `Unable to request OTP:\n${e}`, - code: "INTERNAL_SERVER_ERROR", - }); + throw new Error(`Unable to update user`); } } -async function verifyOtp(token: string, pin: string) { - try { - let pt = await db.query.phoneToken.findFirst({ - where: (pt, { eq }) => eq(pt.token, token), - orderBy: (pt, { desc }) => desc(pt.createdOn), - }); - if (pt === undefined) { - throw new TRPCError({ - message: `Invalid token`, - code: "BAD_REQUEST", - }); - } - pt; - let rs = await fetch(Config.sms_api_verify_endpoint, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - key: Config.sms_api_key, - secret: Config.sms_api_secret, - token, - pin, - }), - }).then((res) => res.json()); - if (rs.errors) { - console.error(rs); - throw new TRPCError({ - message: `Unable to verify OTP`, - code: "BAD_REQUEST", - }); - } else { - await db.delete(phoneToken).where(eq(phoneToken.phone, pt.phone)); - const token = jwt.sign({ phone: pt.phone }, Config.jwt_secret, { - expiresIn: "365d", - }); - return token; - } - } catch (e) { - console.error(e); +async function login(cid: string, phone: string) { + let user = await db.query.user.findFirst({ + where: (user, { and, eq }) => and(eq(user.cid, cid), eq(user.phone, phone)), + }); + if (user === undefined) { throw new TRPCError({ - message: `Unable to verify OTP:\n${e}`, + message: "Invalid Credentials", code: "BAD_REQUEST", }); + } else { + return { token: createJWT(user.phone) }; } } +function createJWT(phone: string) { + return jwt.sign({ phone: phone }, Config.jwt_secret, { + expiresIn: "365d", + }); +} + async function changeOpinion( opinionId: number, userId: number, @@ -229,3 +175,14 @@ async function changeOpinion( }); } } +function isValidThaiID(id: string) { + if (!/^\d{13}$/.test(id)) { + return false; + } + let sum = 0; + for (let i = 0; i < 12; i++) { + sum += Number(id[i]) * (13 - i); + } + const checkSum = (11 - (sum % 11)) % 10; + return checkSum === Number(id[12]); +}