diff --git a/worker/src/i18n/en.ts b/worker/src/i18n/en.ts index d186e4a0..57e23f53 100644 --- a/worker/src/i18n/en.ts +++ b/worker/src/i18n/en.ts @@ -20,6 +20,24 @@ const messages: LocaleMessages = { InvalidAddressMsg: "Invalid address", InvalidAddressCredentialMsg: "Invalid address credential", UserDeleteEmailDisabledMsg: "User delete address/email is disabled, please contact the administrator", + + UserNotFoundMsg: "User not found", + UserAlreadyExistsMsg: "User already exists, please login", + FailedToRegisterMsg: "Failed to register", + UserRegistrationDisabledMsg: "User registration is disabled, please contact the administrator", + UserMailDomainMustInMsg: "User mail domain must be in this list", + InvalidVerifyCodeMsg: "Invalid verify code", + InvalidEmailOrPasswordMsg: "Invalid email or password", + VerifyMailSenderNotSetMsg: "Verify mail sender address is not set, please contact the administrator", + CodeAlreadySentMsg: "Code already sent, please wait", + InvalidUserDefaultRoleMsg: "Invalid user default role, please contact the administrator", + FailedUpdateUserDefaultRoleMsg: "Failed to update user default role, please contact the administrator", + + Oauth2ClientIDNotFoundMsg: "Oauth2 client ID is not set, please contact the administrator", + Oauth2CliendIDOrCodeMissingMsg: "Oauth2 client ID or code is missing", + Oauth2FailedGetUserInfoMsg: "Failed to get user info from Oauth2 provider", + Oauth2FailedGetAccessTokenMsg: "Failed to get access token from Oauth2 provider", + Oauth2FailedGetUserEmailMsg: "Failed to get user email from Oauth2 provider", } export default messages; diff --git a/worker/src/i18n/type.ts b/worker/src/i18n/type.ts index 44b07de6..2f31e599 100644 --- a/worker/src/i18n/type.ts +++ b/worker/src/i18n/type.ts @@ -18,4 +18,22 @@ export type LocaleMessages = { InvalidAddressMsg: string InvalidAddressCredentialMsg: string UserDeleteEmailDisabledMsg: string + + UserNotFoundMsg: string + UserAlreadyExistsMsg: string + FailedToRegisterMsg: string + UserRegistrationDisabledMsg: string + UserMailDomainMustInMsg: string + InvalidVerifyCodeMsg: string + InvalidEmailOrPasswordMsg: string + VerifyMailSenderNotSetMsg: string + CodeAlreadySentMsg: string + InvalidUserDefaultRoleMsg: string + FailedUpdateUserDefaultRoleMsg: string + + Oauth2ClientIDNotFoundMsg: string + Oauth2CliendIDOrCodeMissingMsg: string + Oauth2FailedGetUserInfoMsg: string + Oauth2FailedGetAccessTokenMsg: string + Oauth2FailedGetUserEmailMsg: string } diff --git a/worker/src/i18n/zh.ts b/worker/src/i18n/zh.ts index 49c34678..b55d0cd2 100644 --- a/worker/src/i18n/zh.ts +++ b/worker/src/i18n/zh.ts @@ -18,8 +18,26 @@ const messages: LocaleMessages = { NewAddressAnonymousDisabledMsg: "匿名用户新建邮箱地址已禁用, 请联系管理员", FailedCreateAddressMsg: "创建邮箱地址失败", InvalidAddressMsg: "无效的邮箱地址", - InvalidAddressCredentialMsg: "无效的邮箱地址凭证", + InvalidAddressCredentialMsg: "无效的邮箱地址凭据", UserDeleteEmailDisabledMsg: "用户删除邮箱/邮件已禁用, 请联系管理员", + + UserNotFoundMsg: "用户不存在", + UserAlreadyExistsMsg: "用户已存在, 请登录", + FailedToRegisterMsg: "注册失败", + UserRegistrationDisabledMsg: "用户注册已禁用, 请联系管理员", + UserMailDomainMustInMsg: "用户邮箱域必须在此列表中", + InvalidVerifyCodeMsg: "无效的验证码", + InvalidEmailOrPasswordMsg: "无效的邮箱或密码", + VerifyMailSenderNotSetMsg: "验证邮件发送邮箱未设置, 请联系管理员", + CodeAlreadySentMsg: "验证码已发送, 请稍等", + InvalidUserDefaultRoleMsg: "无效的用户默认角色, 请联系管理员", + FailedUpdateUserDefaultRoleMsg: "更新用户默认角色失败, 请联系管理员", + + Oauth2ClientIDNotFoundMsg: "Oauth2 客户端 ID 未设置, 请联系管理员", + Oauth2CliendIDOrCodeMissingMsg: "Oauth2 客户端 ID 或 code 缺失", + Oauth2FailedGetUserInfoMsg: "从 Oauth2 提供商获取用户信息失败", + Oauth2FailedGetAccessTokenMsg: "从 Oauth2 提供商获取访问令牌失败", + Oauth2FailedGetUserEmailMsg: "从 Oauth2 提供商获取用户邮箱失败", } export default messages; diff --git a/worker/src/user_api/oauth2.ts b/worker/src/user_api/oauth2.ts index 7ff1c779..46a0be12 100644 --- a/worker/src/user_api/oauth2.ts +++ b/worker/src/user_api/oauth2.ts @@ -1,6 +1,7 @@ import { Context } from 'hono'; import { Jwt } from 'hono/utils/jwt' +import i18n from '../i18n'; import { HonoCustomType } from '../types'; import { getJsonSetting } from '../utils'; import { UserOauth2Settings } from '../models'; @@ -11,22 +12,26 @@ export default { getOauth2LoginUrl: async (c: Context) => { const settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY); const { clientID, state } = c.req.query(); + const lang = c.get("lang") || c.env.DEFAULT_LANG; + const msgs = i18n.getMessages(lang); const setting = settings?.find(s => s.clientID === clientID); if (!setting) { - return c.text("Client not found", 400); + return c.text(msgs.Oauth2ClientIDNotFoundMsg, 400); } const url = `${setting.authorizationURL}?client_id=${setting.clientID}&response_type=code&redirect_uri=${setting.redirectURL}&scope=${setting.scope}&state=${state}` return c.json({ url }); }, oauth2Login: async (c: Context) => { const { clientID, code } = await c.req.json<{ clientID?: string, code?: string }>(); + const lang = c.get("lang") || c.env.DEFAULT_LANG; + const msgs = i18n.getMessages(lang); if (!clientID || !code) { - return c.text("clientID or code is missing", 400); + return c.text(msgs.Oauth2CliendIDOrCodeMissingMsg, 400); } const settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY); const setting = settings?.find(s => s.clientID === clientID); if (!setting) { - return c.text("Client not found", 400); + return c.text(msgs.Oauth2ClientIDNotFoundMsg, 400); } const params = { code, @@ -48,7 +53,7 @@ export default { }) if (!res.ok) { console.error(`Failed to get access token: ${res.status} ${res.statusText} ${await res.text()}`) - return c.text("Failed to get access token", 400); + return c.text(msgs.Oauth2FailedGetAccessTokenMsg, 400); } const resJson = await res.json(); const { access_token, token_type } = resJson as { access_token: string, token_type?: string }; @@ -61,17 +66,17 @@ export default { }) if (!user.ok) { console.error(`Failed to get user info: ${res.status} ${res.statusText} ${await res.text()}`) - return c.text("Failed to get user info", 400); + return c.text(msgs.Oauth2FailedGetUserInfoMsg, 400); } const userInfo = await user.json() const { [setting.userEmailKey]: email } = userInfo as { [key: string]: string }; if (!email) { - return c.text("Failed to get user email", 400); + return c.text(msgs.Oauth2FailedGetUserEmailMsg, 400); } // check email in mail allow list const mailDomain = email.split("@")[1]; if (setting.enableMailAllowList && !setting.mailAllowList?.includes(mailDomain)) { - return c.text(`Mail domain must in ${JSON.stringify(setting.mailAllowList, null, 2)}`, 400) + return c.text(`${msgs.UserMailDomainMustInMsg} ${JSON.stringify(setting.mailAllowList, null, 2)}`, 400) } // insert or update user const { success } = await c.env.DB.prepare( @@ -82,13 +87,13 @@ export default { email, JSON.stringify(userInfo) ).run(); if (!success) { - return c.text("Failed to register", 500) + return c.text(msgs.FailedToRegisterMsg, 500) } const { id: user_id } = await c.env.DB.prepare( `SELECT id FROM users where user_email = ?` ).bind(email).first() || {}; if (!user_id) { - return c.text("User not found", 400) + return c.text(msgs.UserNotFoundMsg, 400) } // create jwt const jwt = await Jwt.sign({ diff --git a/worker/src/user_api/settings.ts b/worker/src/user_api/settings.ts index 47fe0cc4..59b30436 100644 --- a/worker/src/user_api/settings.ts +++ b/worker/src/user_api/settings.ts @@ -1,5 +1,6 @@ import { Context } from "hono"; +import i18n from "../i18n"; import { HonoCustomType } from "../types"; import { UserOauth2Settings, UserSettings } from "../models"; import { getJsonSetting, getUserRoles } from "../utils" @@ -31,12 +32,14 @@ export default { }, settings: async (c: Context) => { const user = c.get("userPayload"); + const lang = c.get("lang") || c.env.DEFAULT_LANG; + const msgs = i18n.getMessages(lang); // check if user exists const db_user_id = await c.env.DB.prepare( `SELECT id FROM users where id = ?` ).bind(user.user_id).first("id"); if (!db_user_id) { - return c.text("User not found", 400); + return c.text(msgs.UserNotFoundMsg, 400); } const user_role = await commonGetUserRole(c, db_user_id); const is_admin = ( diff --git a/worker/src/user_api/user.ts b/worker/src/user_api/user.ts index 8f7f3a41..c75e313f 100644 --- a/worker/src/user_api/user.ts +++ b/worker/src/user_api/user.ts @@ -27,15 +27,15 @@ export default { && settings.mailAllowList && !settings.mailAllowList.includes(mailDomain) ) { - return c.text(`Mail domain must in ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400) + return c.text(`${msgs.UserMailDomainMustInMsg} ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400) } if (!settings.verifyMailSender) { - return c.text("Verify mail sender not set", 400) + return c.text(msgs.VerifyMailSenderNotSetMsg, 400) } // check if code exists in KV const tmpcode = await c.env.KV.get(`temp-mail:${email}`) if (tmpcode) { - return c.text("Code already sent, please wait", 400) + return c.text(msgs.CodeAlreadySentMsg, 400) } // generate code 6 digits and convert to string const code = Math.floor(100000 + Math.random() * 900000).toString(); @@ -62,18 +62,20 @@ export default { register: async (c: Context) => { const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY); const settings = new UserSettings(value) + const lang = c.get("lang") || c.env.DEFAULT_LANG; + const msgs = i18n.getMessages(lang); // check enable if (!settings.enable) { - return c.text("User registration is disabled"); + return c.text(msgs.UserRegistrationDisabledMsg, 403); } // check request const { email, password, code } = await c.req.json(); if (!email || !password) { - return c.text("Invalid email or password", 400) + return c.text(msgs.InvalidEmailOrPasswordMsg, 400) } checkUserPassword(password); if (settings.enableMailVerify && !code) { - return c.text("Need verify code", 400) + return c.text(msgs.InvalidVerifyCodeMsg, 400) } // check mail domain allow list const mailDomain = email.split("@")[1]; @@ -81,13 +83,13 @@ export default { && settings.mailAllowList && !settings.mailAllowList.includes(mailDomain) ) { - return c.text(`Mail domain must in ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400) + return c.text(`${msgs.UserMailDomainMustInMsg} ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400) } // check code if (settings.enableMailVerify) { const verifyCode = await c.env.KV.get(`temp-mail:${email}`) if (verifyCode != code) { - return c.text("Invalid verify code", 400) + return c.text(msgs.InvalidVerifyCodeMsg, 400) } } // geo data @@ -104,14 +106,14 @@ export default { email, password, JSON.stringify(userInfo) ).run(); if (!success) { - return c.text("Failed to register", 500) + return c.text(msgs.FailedToRegisterMsg, 500) } } catch (e) { const error = e as Error; if (error.message && error.message.includes("UNIQUE")) { - return c.text("User already exists, please login", 400) + return c.text(msgs.UserAlreadyExistsMsg, 400) } - return c.text(`Failed to register: ${error.message}`, 500) + return c.text(`${msgs.FailedToRegisterMsg}: ${error.message}`, 500) } return c.json({ success: true }) } @@ -125,20 +127,20 @@ export default { password, JSON.stringify(userInfo) ).run(); if (!success) { - return c.text("Failed to register", 500) + return c.text(msgs.FailedToRegisterMsg, 400); } const defaultRole = getStringValue(c.env.USER_DEFAULT_ROLE); if (!defaultRole) return c.json({ success: true }) const user_roles = getUserRoles(c); if (!user_roles.find((r) => r.role === defaultRole)) { - return c.text("Invalid role_text", 400) + return c.text(msgs.InvalidUserDefaultRoleMsg, 500); } // find user_id const user_id = await c.env.DB.prepare( `SELECT id FROM users where user_email = ?` ).bind(email).first("id"); if (!user_id) { - return c.text("User not found", 400) + return c.text(msgs.UserNotFoundMsg, 500); } // update user roles const { success: success2 } = await c.env.DB.prepare( @@ -147,22 +149,24 @@ export default { + ` ON CONFLICT(user_id) DO NOTHING` ).bind(user_id, defaultRole).run(); if (!success2) { - return c.text("Failed to update user roles", 500) + return c.text(msgs.FailedUpdateUserDefaultRoleMsg, 500); } return c.json({ success: true }) }, login: async (c: Context) => { const { email, password } = await c.req.json(); - if (!email || !password) return c.text("Invalid email or password", 400); + const lang = c.get("lang") || c.env.DEFAULT_LANG; + const msgs = i18n.getMessages(lang); + if (!email || !password) return c.text(msgs.InvalidEmailOrPasswordMsg, 400); const { id: user_id, password: dbPassword } = await c.env.DB.prepare( `SELECT id, password FROM users where user_email = ?` ).bind(email).first() || {}; if (!dbPassword) { - return c.text("User not found", 400) + return c.text(msgs.UserNotFoundMsg, 400) } // TODO: need check password use random salt if (dbPassword != password) { - return c.text("Invalid password", 400) + return c.text(msgs.InvalidEmailOrPasswordMsg, 400) } // create jwt const jwt = await Jwt.sign({