feat: |Worker| multi language add messages (#587)

This commit is contained in:
Dream Hunter
2025-02-20 01:41:34 +08:00
committed by GitHub
parent b43353ea47
commit 241e0b7b28
6 changed files with 95 additions and 29 deletions
+18
View File
@@ -20,6 +20,24 @@ const messages: LocaleMessages = {
InvalidAddressMsg: "Invalid address", InvalidAddressMsg: "Invalid address",
InvalidAddressCredentialMsg: "Invalid address credential", InvalidAddressCredentialMsg: "Invalid address credential",
UserDeleteEmailDisabledMsg: "User delete address/email is disabled, please contact the administrator", 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; export default messages;
+18
View File
@@ -18,4 +18,22 @@ export type LocaleMessages = {
InvalidAddressMsg: string InvalidAddressMsg: string
InvalidAddressCredentialMsg: string InvalidAddressCredentialMsg: string
UserDeleteEmailDisabledMsg: 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
} }
+19 -1
View File
@@ -18,8 +18,26 @@ const messages: LocaleMessages = {
NewAddressAnonymousDisabledMsg: "匿名用户新建邮箱地址已禁用, 请联系管理员", NewAddressAnonymousDisabledMsg: "匿名用户新建邮箱地址已禁用, 请联系管理员",
FailedCreateAddressMsg: "创建邮箱地址失败", FailedCreateAddressMsg: "创建邮箱地址失败",
InvalidAddressMsg: "无效的邮箱地址", InvalidAddressMsg: "无效的邮箱地址",
InvalidAddressCredentialMsg: "无效的邮箱地址凭", InvalidAddressCredentialMsg: "无效的邮箱地址凭",
UserDeleteEmailDisabledMsg: "用户删除邮箱/邮件已禁用, 请联系管理员", 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; export default messages;
+14 -9
View File
@@ -1,6 +1,7 @@
import { Context } from 'hono'; import { Context } from 'hono';
import { Jwt } from 'hono/utils/jwt' import { Jwt } from 'hono/utils/jwt'
import i18n from '../i18n';
import { HonoCustomType } from '../types'; import { HonoCustomType } from '../types';
import { getJsonSetting } from '../utils'; import { getJsonSetting } from '../utils';
import { UserOauth2Settings } from '../models'; import { UserOauth2Settings } from '../models';
@@ -11,22 +12,26 @@ export default {
getOauth2LoginUrl: async (c: Context<HonoCustomType>) => { getOauth2LoginUrl: async (c: Context<HonoCustomType>) => {
const settings = await getJsonSetting<UserOauth2Settings[]>(c, CONSTANTS.OAUTH2_SETTINGS_KEY); const settings = await getJsonSetting<UserOauth2Settings[]>(c, CONSTANTS.OAUTH2_SETTINGS_KEY);
const { clientID, state } = c.req.query(); 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); const setting = settings?.find(s => s.clientID === clientID);
if (!setting) { 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}` const url = `${setting.authorizationURL}?client_id=${setting.clientID}&response_type=code&redirect_uri=${setting.redirectURL}&scope=${setting.scope}&state=${state}`
return c.json({ url }); return c.json({ url });
}, },
oauth2Login: async (c: Context<HonoCustomType>) => { oauth2Login: async (c: Context<HonoCustomType>) => {
const { clientID, code } = await c.req.json<{ clientID?: string, code?: string }>(); 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) { if (!clientID || !code) {
return c.text("clientID or code is missing", 400); return c.text(msgs.Oauth2CliendIDOrCodeMissingMsg, 400);
} }
const settings = await getJsonSetting<UserOauth2Settings[]>(c, CONSTANTS.OAUTH2_SETTINGS_KEY); const settings = await getJsonSetting<UserOauth2Settings[]>(c, CONSTANTS.OAUTH2_SETTINGS_KEY);
const setting = settings?.find(s => s.clientID === clientID); const setting = settings?.find(s => s.clientID === clientID);
if (!setting) { if (!setting) {
return c.text("Client not found", 400); return c.text(msgs.Oauth2ClientIDNotFoundMsg, 400);
} }
const params = { const params = {
code, code,
@@ -48,7 +53,7 @@ export default {
}) })
if (!res.ok) { if (!res.ok) {
console.error(`Failed to get access token: ${res.status} ${res.statusText} ${await res.text()}`) 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 resJson = await res.json();
const { access_token, token_type } = resJson as { access_token: string, token_type?: string }; const { access_token, token_type } = resJson as { access_token: string, token_type?: string };
@@ -61,17 +66,17 @@ export default {
}) })
if (!user.ok) { if (!user.ok) {
console.error(`Failed to get user info: ${res.status} ${res.statusText} ${await res.text()}`) 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 userInfo = await user.json()
const { [setting.userEmailKey]: email } = userInfo as { [key: string]: string }; const { [setting.userEmailKey]: email } = userInfo as { [key: string]: string };
if (!email) { if (!email) {
return c.text("Failed to get user email", 400); return c.text(msgs.Oauth2FailedGetUserEmailMsg, 400);
} }
// check email in mail allow list // check email in mail allow list
const mailDomain = email.split("@")[1]; const mailDomain = email.split("@")[1];
if (setting.enableMailAllowList && !setting.mailAllowList?.includes(mailDomain)) { 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 // insert or update user
const { success } = await c.env.DB.prepare( const { success } = await c.env.DB.prepare(
@@ -82,13 +87,13 @@ export default {
email, JSON.stringify(userInfo) email, JSON.stringify(userInfo)
).run(); ).run();
if (!success) { if (!success) {
return c.text("Failed to register", 500) return c.text(msgs.FailedToRegisterMsg, 500)
} }
const { id: user_id } = await c.env.DB.prepare( const { id: user_id } = await c.env.DB.prepare(
`SELECT id FROM users where user_email = ?` `SELECT id FROM users where user_email = ?`
).bind(email).first() || {}; ).bind(email).first() || {};
if (!user_id) { if (!user_id) {
return c.text("User not found", 400) return c.text(msgs.UserNotFoundMsg, 400)
} }
// create jwt // create jwt
const jwt = await Jwt.sign({ const jwt = await Jwt.sign({
+4 -1
View File
@@ -1,5 +1,6 @@
import { Context } from "hono"; import { Context } from "hono";
import i18n from "../i18n";
import { HonoCustomType } from "../types"; import { HonoCustomType } from "../types";
import { UserOauth2Settings, UserSettings } from "../models"; import { UserOauth2Settings, UserSettings } from "../models";
import { getJsonSetting, getUserRoles } from "../utils" import { getJsonSetting, getUserRoles } from "../utils"
@@ -31,12 +32,14 @@ export default {
}, },
settings: async (c: Context<HonoCustomType>) => { settings: async (c: Context<HonoCustomType>) => {
const user = c.get("userPayload"); const user = c.get("userPayload");
const lang = c.get("lang") || c.env.DEFAULT_LANG;
const msgs = i18n.getMessages(lang);
// check if user exists // check if user exists
const db_user_id = await c.env.DB.prepare( const db_user_id = await c.env.DB.prepare(
`SELECT id FROM users where id = ?` `SELECT id FROM users where id = ?`
).bind(user.user_id).first<number | undefined | null>("id"); ).bind(user.user_id).first<number | undefined | null>("id");
if (!db_user_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 user_role = await commonGetUserRole(c, db_user_id);
const is_admin = ( const is_admin = (
+22 -18
View File
@@ -27,15 +27,15 @@ export default {
&& settings.mailAllowList && settings.mailAllowList
&& !settings.mailAllowList.includes(mailDomain) && !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) { if (!settings.verifyMailSender) {
return c.text("Verify mail sender not set", 400) return c.text(msgs.VerifyMailSenderNotSetMsg, 400)
} }
// check if code exists in KV // check if code exists in KV
const tmpcode = await c.env.KV.get(`temp-mail:${email}`) const tmpcode = await c.env.KV.get(`temp-mail:${email}`)
if (tmpcode) { 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 // generate code 6 digits and convert to string
const code = Math.floor(100000 + Math.random() * 900000).toString(); const code = Math.floor(100000 + Math.random() * 900000).toString();
@@ -62,18 +62,20 @@ export default {
register: async (c: Context<HonoCustomType>) => { register: async (c: Context<HonoCustomType>) => {
const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY); const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
const settings = new UserSettings(value) const settings = new UserSettings(value)
const lang = c.get("lang") || c.env.DEFAULT_LANG;
const msgs = i18n.getMessages(lang);
// check enable // check enable
if (!settings.enable) { if (!settings.enable) {
return c.text("User registration is disabled"); return c.text(msgs.UserRegistrationDisabledMsg, 403);
} }
// check request // check request
const { email, password, code } = await c.req.json(); const { email, password, code } = await c.req.json();
if (!email || !password) { if (!email || !password) {
return c.text("Invalid email or password", 400) return c.text(msgs.InvalidEmailOrPasswordMsg, 400)
} }
checkUserPassword(password); checkUserPassword(password);
if (settings.enableMailVerify && !code) { if (settings.enableMailVerify && !code) {
return c.text("Need verify code", 400) return c.text(msgs.InvalidVerifyCodeMsg, 400)
} }
// check mail domain allow list // check mail domain allow list
const mailDomain = email.split("@")[1]; const mailDomain = email.split("@")[1];
@@ -81,13 +83,13 @@ export default {
&& settings.mailAllowList && settings.mailAllowList
&& !settings.mailAllowList.includes(mailDomain) && !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 // check code
if (settings.enableMailVerify) { if (settings.enableMailVerify) {
const verifyCode = await c.env.KV.get(`temp-mail:${email}`) const verifyCode = await c.env.KV.get(`temp-mail:${email}`)
if (verifyCode != code) { if (verifyCode != code) {
return c.text("Invalid verify code", 400) return c.text(msgs.InvalidVerifyCodeMsg, 400)
} }
} }
// geo data // geo data
@@ -104,14 +106,14 @@ export default {
email, password, JSON.stringify(userInfo) email, password, JSON.stringify(userInfo)
).run(); ).run();
if (!success) { if (!success) {
return c.text("Failed to register", 500) return c.text(msgs.FailedToRegisterMsg, 500)
} }
} catch (e) { } catch (e) {
const error = e as Error; const error = e as Error;
if (error.message && error.message.includes("UNIQUE")) { 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 }) return c.json({ success: true })
} }
@@ -125,20 +127,20 @@ export default {
password, JSON.stringify(userInfo) password, JSON.stringify(userInfo)
).run(); ).run();
if (!success) { if (!success) {
return c.text("Failed to register", 500) return c.text(msgs.FailedToRegisterMsg, 400);
} }
const defaultRole = getStringValue(c.env.USER_DEFAULT_ROLE); const defaultRole = getStringValue(c.env.USER_DEFAULT_ROLE);
if (!defaultRole) return c.json({ success: true }) if (!defaultRole) return c.json({ success: true })
const user_roles = getUserRoles(c); const user_roles = getUserRoles(c);
if (!user_roles.find((r) => r.role === defaultRole)) { if (!user_roles.find((r) => r.role === defaultRole)) {
return c.text("Invalid role_text", 400) return c.text(msgs.InvalidUserDefaultRoleMsg, 500);
} }
// find user_id // find user_id
const user_id = await c.env.DB.prepare( const user_id = await c.env.DB.prepare(
`SELECT id FROM users where user_email = ?` `SELECT id FROM users where user_email = ?`
).bind(email).first<number | undefined | null>("id"); ).bind(email).first<number | undefined | null>("id");
if (!user_id) { if (!user_id) {
return c.text("User not found", 400) return c.text(msgs.UserNotFoundMsg, 500);
} }
// update user roles // update user roles
const { success: success2 } = await c.env.DB.prepare( const { success: success2 } = await c.env.DB.prepare(
@@ -147,22 +149,24 @@ export default {
+ ` ON CONFLICT(user_id) DO NOTHING` + ` ON CONFLICT(user_id) DO NOTHING`
).bind(user_id, defaultRole).run(); ).bind(user_id, defaultRole).run();
if (!success2) { if (!success2) {
return c.text("Failed to update user roles", 500) return c.text(msgs.FailedUpdateUserDefaultRoleMsg, 500);
} }
return c.json({ success: true }) return c.json({ success: true })
}, },
login: async (c: Context<HonoCustomType>) => { login: async (c: Context<HonoCustomType>) => {
const { email, password } = await c.req.json(); 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( const { id: user_id, password: dbPassword } = await c.env.DB.prepare(
`SELECT id, password FROM users where user_email = ?` `SELECT id, password FROM users where user_email = ?`
).bind(email).first() || {}; ).bind(email).first() || {};
if (!dbPassword) { if (!dbPassword) {
return c.text("User not found", 400) return c.text(msgs.UserNotFoundMsg, 400)
} }
// TODO: need check password use random salt // TODO: need check password use random salt
if (dbPassword != password) { if (dbPassword != password) {
return c.text("Invalid password", 400) return c.text(msgs.InvalidEmailOrPasswordMsg, 400)
} }
// create jwt // create jwt
const jwt = await Jwt.sign({ const jwt = await Jwt.sign({