diff --git a/CHANGELOG.md b/CHANGELOG.md index 276550f4..733f0923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - feat: worker 增加 `ADDRESS_CHECK_REGEX`, address name 的正则表达式, 只用于检查,符合条件将通过检查 - fix: UI 修复登录页面 tab 激活图标错位 - fix: UI 修复 admin 页面刷新弹框输入密码的问题 +- feat: support `Oath2` 登录, 可以通过 `Github` `Authentik` 等第三方登录, 详情查看 [OAuth2 第三方登录](https://temp-mail-docs.awsl.uk/zh/guide/feature/user-oauth2.html) ## v0.7.2 diff --git a/frontend/src/constant/index.ts b/frontend/src/constant/index.ts new file mode 100644 index 00000000..36cb8ab5 --- /dev/null +++ b/frontend/src/constant/index.ts @@ -0,0 +1,8 @@ +const COMMOM_MAIL = [ + "gmail.com", "163.com", "126.com", "qq.com", "outlook.com", "hotmail.com", + "icloud.com", "yahoo.com", "foxmail.com" +] + +export default { + COMMOM_MAIL +} diff --git a/frontend/src/models/index.ts b/frontend/src/models/index.ts new file mode 100644 index 00000000..1fbaf1ab --- /dev/null +++ b/frontend/src/models/index.ts @@ -0,0 +1,15 @@ +export type UserOauth2Settings = { + name: string; + clientID: string; + clientSecret: string; + authorizationURL: string; + accessTokenURL: string; + accessTokenFormat?: string; + userInfoURL: string; + redirectURL: string; + logoutURL?: string; + userEmailKey: string; + scope: string; + enableMailAllowList?: boolean | undefined; + mailAllowList?: string[] | undefined; +} diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 27bca6a9..e3323334 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -1,7 +1,7 @@ import { createRouter, createWebHistory } from 'vue-router' import Index from '../views/Index.vue' import User from '../views/User.vue' -import { useGlobalState } from '../store' +import UserOauth2Callback from '../views/user/UserOauth2Callback.vue' const router = createRouter({ history: createWebHistory(), @@ -16,6 +16,11 @@ const router = createRouter({ alias: "/:lang/user", component: User }, + { + path: '/user/oauth2/callback', + alias: "/:lang/user/oauth2/callback", + component: UserOauth2Callback + }, { path: '/admin', alias: "/:lang/admin", diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 71b86143..46ad1de6 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -1,5 +1,8 @@ import { computed, ref } from "vue"; -import { createGlobalState, useStorage, useDark, useToggle, useLocalStorage } from '@vueuse/core' +import { + createGlobalState, useStorage, useDark, useToggle, + useLocalStorage, useSessionStorage +} from '@vueuse/core' export const useGlobalState = createGlobalState( () => { @@ -42,7 +45,7 @@ export const useGlobalState = createGlobalState( name: '', } }); - const sendMailModel = useStorage('sendMailModel', { + const sendMailModel = useSessionStorage('sendMailModel', { fromName: "", toName: "", toMail: "", @@ -56,21 +59,23 @@ export const useGlobalState = createGlobalState( const auth = useStorage('auth', ''); const adminAuth = useStorage('adminAuth', ''); const jwt = useStorage('jwt', ''); - const adminTab = ref("account"); + const adminTab = useSessionStorage('adminTab', "account"); const adminMailTabAddress = ref(""); const adminSendBoxTabAddress = ref(""); const mailboxSplitSize = useStorage('mailboxSplitSize', 0.25); const useIframeShowMail = useStorage('useIframeShowMail', false); const preferShowTextMail = useStorage('preferShowTextMail', false); const userJwt = useStorage('userJwt', ''); - const userTab = useStorage('userTab', 'user_settings'); - const indexTab = useStorage('indexTab', 'mailbox'); + const userTab = useSessionStorage('userTab', 'user_settings'); + const indexTab = useSessionStorage('indexTab', 'mailbox'); const globalTabplacement = useStorage('globalTabplacement', 'top'); const useSideMargin = useStorage('useSideMargin', true); const userOpenSettings = ref({ fetched: false, enable: false, enableMailVerify: false, + /** @type {{ clientID: string, name: string }[]} */ + oauth2ClientIDs: [], }); const userSettings = ref({ /** @type {boolean} */ @@ -93,6 +98,8 @@ export const useGlobalState = createGlobalState( ); const telegramApp = ref(window.Telegram?.WebApp || {}); const isTelegram = ref(!!window.Telegram?.WebApp?.initData); + const userOauth2SessionState = useSessionStorage('userOauth2SessionState', ''); + const userOauth2SessionClientID = useSessionStorage('userOauth2SessionClientID', ''); return { isDark, toggleDark, @@ -123,6 +130,8 @@ export const useGlobalState = createGlobalState( telegramApp, isTelegram, showAdminPage, + userOauth2SessionState, + userOauth2SessionClientID, } }, ) diff --git a/frontend/src/views/Admin.vue b/frontend/src/views/Admin.vue index 0a09a688..9f2f8f10 100644 --- a/frontend/src/views/Admin.vue +++ b/frontend/src/views/Admin.vue @@ -13,6 +13,7 @@ import CreateAccount from './admin/CreateAccount.vue'; import AccountSettings from './admin/AccountSettings.vue'; import UserManagement from './admin/UserManagement.vue'; import UserSettings from './admin/UserSettings.vue'; +import UserOauth2Settings from './admin/UserOauth2Settings.vue'; import Mails from './admin/Mails.vue'; import MailsUnknow from './admin/MailsUnknow.vue'; import About from './common/About.vue'; @@ -49,6 +50,7 @@ const { t } = useI18n({ user: 'User', user_management: 'User Management', user_settings: 'User Settings', + userOauth2Settings: 'Oauth2 Settings', unknow: 'Mails with unknow receiver', senderAccess: 'Sender Access Control', sendBox: 'Send Box', @@ -71,6 +73,7 @@ const { t } = useI18n({ user: '用户', user_management: '用户管理', user_settings: '用户设置', + userOauth2Settings: 'Oauth2 设置', unknow: '无收件人邮件', senderAccess: '发件权限控制', sendBox: '发件箱', @@ -135,6 +138,9 @@ onMounted(async () => { + + + diff --git a/frontend/src/views/admin/UserOauth2Settings.vue b/frontend/src/views/admin/UserOauth2Settings.vue new file mode 100644 index 00000000..1567e6b9 --- /dev/null +++ b/frontend/src/views/admin/UserOauth2Settings.vue @@ -0,0 +1,253 @@ + + + + + diff --git a/frontend/src/views/user/UserLogin.vue b/frontend/src/views/user/UserLogin.vue index 70d9a5f0..da4f5bdb 100644 --- a/frontend/src/views/user/UserLogin.vue +++ b/frontend/src/views/user/UserLogin.vue @@ -2,6 +2,7 @@ import { useMessage } from 'naive-ui' import { onMounted, ref } from "vue"; import { useI18n } from 'vue-i18n' +import { KeyFilled } from '@vicons/material' import { api } from '../../api'; import { useGlobalState } from '../../store' @@ -10,7 +11,10 @@ import { startAuthentication } from '@simplewebauthn/browser'; import Turnstile from '../../components/Turnstile.vue'; -const { userJwt, userOpenSettings, openSettings } = useGlobalState() +const { + userJwt, userOpenSettings, openSettings, + userOauth2SessionState, userOauth2SessionClientID +} = useGlobalState() const message = useMessage(); const { t } = useI18n({ @@ -33,6 +37,7 @@ const { t } = useI18n({ pleaseCompleteTurnstile: 'Please complete turnstile', pleaseLogin: 'Please login', loginWithPasskey: 'Login with Passkey', + loginWith: 'Login with {provider}', }, zh: { login: '登录', @@ -52,6 +57,7 @@ const { t } = useI18n({ pleaseCompleteTurnstile: '请完成人机验证', pleaseLogin: '请登录', loginWithPasskey: '使用 Passkey 登录', + loginWith: '使用 {provider} 登录', } } }); @@ -184,6 +190,18 @@ const passkeyLogin = async () => { } }; +const oauth2Login = async (clientID) => { + try { + userOauth2SessionClientID.value = clientID; + userOauth2SessionState.value = Math.random().toString(36).substring(2); + const res = await api.fetch(`/user_api/oauth2/login_url?clientID=${clientID}&state=${userOauth2SessionState.value}`); + // redirect to oauth2 login page + location.href = res.url; + } catch (error) { + message.error(error.message || "login failed"); + } +}; + onMounted(async () => { }); @@ -208,8 +226,15 @@ onMounted(async () => { + {{ t('loginWithPasskey') }} + + {{ t('loginWith', { provider: item.name }) }} + @@ -276,4 +301,8 @@ onMounted(async () => { place-items: center; justify-content: center; } + +.n-button { + margin-top: 10px; +} diff --git a/frontend/src/views/user/UserOauth2Callback.vue b/frontend/src/views/user/UserOauth2Callback.vue new file mode 100644 index 00000000..7fe81f8e --- /dev/null +++ b/frontend/src/views/user/UserOauth2Callback.vue @@ -0,0 +1,65 @@ + + + diff --git a/vitepress-docs/docs/.vitepress/zh.ts b/vitepress-docs/docs/.vitepress/zh.ts index f9998937..bf518a43 100644 --- a/vitepress-docs/docs/.vitepress/zh.ts +++ b/vitepress-docs/docs/.vitepress/zh.ts @@ -96,7 +96,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { }, { text: '通过命令行部署', - collapsed: true, + collapsed: false, items: [ { text: '命令行部署准备', link: 'cli/pre-requisite' }, { text: 'D1 数据库', link: 'cli/d1' }, @@ -108,7 +108,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { }, { text: '通过用户界面部署', - collapsed: true, + collapsed: false, items: [ { text: 'D1 数据库', link: 'ui/d1' }, { text: 'Cloudflare workers 后端', link: 'ui/worker' }, @@ -119,14 +119,14 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { }, { text: '通过 Github Actions 部署', - collapsed: true, + collapsed: false, items: [ { text: '通过 Github Actions 部署', link: 'github-action' }, ] }, { text: '附加功能', - collapsed: true, + collapsed: false, items: [ { text: '配置 SMTP IMAP 代理服务', link: 'feature/config-smtp-proxy' }, { text: '发送邮件 API', link: 'feature/send-mail-api' }, @@ -137,11 +137,12 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { { text: '配置 worker 使用 wasm 解析邮件', link: 'feature/mail_parser_wasm_worker' }, { text: '配置 webhook', link: 'feature/webhook' }, { text: '新建邮箱地址 API', link: 'feature/new-address-api' }, + { text: 'Oauth2 第三方登录', link: 'feature/user-oauth2' }, ] }, { text: '功能简介', - collapsed: true, + collapsed: false, items: [ { text: 'Admin 控制台', link: 'feature/admin' }, { text: 'Admin 用户管理', link: 'feature/admin-user-management' }, diff --git a/vitepress-docs/docs/public/feature/address-webhook.png b/vitepress-docs/docs/public/feature/address-webhook.png index 2a8092cb..0f6fa4a8 100644 Binary files a/vitepress-docs/docs/public/feature/address-webhook.png and b/vitepress-docs/docs/public/feature/address-webhook.png differ diff --git a/vitepress-docs/docs/public/feature/admin-mail-webhook.png b/vitepress-docs/docs/public/feature/admin-mail-webhook.png index d2cb1c40..87baed8f 100644 Binary files a/vitepress-docs/docs/public/feature/admin-mail-webhook.png and b/vitepress-docs/docs/public/feature/admin-mail-webhook.png differ diff --git a/vitepress-docs/docs/public/feature/admin-user-page.png b/vitepress-docs/docs/public/feature/admin-user-page.png index fecbc5f3..c9c022ed 100644 Binary files a/vitepress-docs/docs/public/feature/admin-user-page.png and b/vitepress-docs/docs/public/feature/admin-user-page.png differ diff --git a/vitepress-docs/docs/public/feature/admin-webhook-settings.png b/vitepress-docs/docs/public/feature/admin-webhook-settings.png index 8b1c6c40..d1dd7ed4 100644 Binary files a/vitepress-docs/docs/public/feature/admin-webhook-settings.png and b/vitepress-docs/docs/public/feature/admin-webhook-settings.png differ diff --git a/vitepress-docs/docs/public/feature/admin.png b/vitepress-docs/docs/public/feature/admin.png index bb7b9b20..42da75c0 100644 Binary files a/vitepress-docs/docs/public/feature/admin.png and b/vitepress-docs/docs/public/feature/admin.png differ diff --git a/vitepress-docs/docs/public/feature/oauth2-login.png b/vitepress-docs/docs/public/feature/oauth2-login.png new file mode 100644 index 00000000..ba880725 Binary files /dev/null and b/vitepress-docs/docs/public/feature/oauth2-login.png differ diff --git a/vitepress-docs/docs/public/feature/oauth2.png b/vitepress-docs/docs/public/feature/oauth2.png new file mode 100644 index 00000000..675e5f84 Binary files /dev/null and b/vitepress-docs/docs/public/feature/oauth2.png differ diff --git a/vitepress-docs/docs/zh/guide/feature/user-oauth2.md b/vitepress-docs/docs/zh/guide/feature/user-oauth2.md new file mode 100644 index 00000000..4e30ef86 --- /dev/null +++ b/vitepress-docs/docs/zh/guide/feature/user-oauth2.md @@ -0,0 +1,26 @@ +# OAuth2 第三方登录 + +> [!WARNING] +> 第三方登录会自动使用用户邮箱注册账号(邮箱相同将视为同一账号) +> +> 此账号和注册的账号相同, 也可以通过忘记密码设置密码 + +## 在第三方平台注册 OAuth2 + +### GitHub + +- 请先创建一个 OAuth App,然后获取 `Client ID` 和 `Client Secret` + +参考 [Creating an OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) + +### Authentik + +- [Authentik OAuth2 Provider](https://docs.goauthentik.io/docs/providers/oauth2/) + +## Admin 后台配置 OAuth2 + +![oauth2](/feature/oauth2.png) + +## 测试用户登录页面 + +![oauth2 login](/feature/oauth2-login.png) diff --git a/worker/src/admin_api/index.ts b/worker/src/admin_api/index.ts index 171f253a..f597de9d 100644 --- a/worker/src/admin_api/index.ts +++ b/worker/src/admin_api/index.ts @@ -9,6 +9,7 @@ import cleanup_api from './cleanup_api' import admin_user_api from './admin_user_api' import webhook_settings from './webhook_settings' import mail_webhook_settings from './mail_webhook_settings' +import oauth2_settings from './oauth2_settings' export const api = new Hono() @@ -313,6 +314,10 @@ api.post('/admin/users/:user_id/reset_password', admin_user_api.resetPassword) api.get('/admin/user_roles', async (c) => c.json(getUserRoles(c))) api.post('/admin/user_roles', admin_user_api.updateUserRoles) +// user oauth2 settings +api.get('/admin/user_oauth2_settings', oauth2_settings.getUserOauth2Settings) +api.post('/admin/user_oauth2_settings', oauth2_settings.saveUserOauth2Settings) + // webhook settings api.get("/admin/webhook/settings", webhook_settings.getWebhookSettings); api.post("/admin/webhook/settings", webhook_settings.saveWebhookSettings); diff --git a/worker/src/admin_api/oauth2_settings.ts b/worker/src/admin_api/oauth2_settings.ts new file mode 100644 index 00000000..48cd9a5a --- /dev/null +++ b/worker/src/admin_api/oauth2_settings.ts @@ -0,0 +1,34 @@ +import { Context } from 'hono'; + +import { CONSTANTS } from '../constants'; +import { UserOauth2Settings } from "../models"; +import { HonoCustomType } from '../types'; +import { getJsonSetting, saveSetting } from '../utils'; + +async function getUserOauth2Settings(c: Context): Promise { + const settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY); + return c.json(settings || []); +} + +async function saveUserOauth2Settings(c: Context): Promise { + const settings = await c.req.json(); + for (const setting of settings) { + if (!setting.name || !setting.clientID || !setting.clientSecret + || !setting.authorizationURL || !setting.accessTokenURL + || !setting.accessTokenFormat + || !setting.userInfoURL || !setting.redirectURL + || !setting.userEmailKey || !setting.scope) { + return c.text(`${setting.name} is missing required fields`, 400); + } + if (setting.enableMailAllowList && (setting.mailAllowList?.length || 0) < 1) { + return c.text(`${setting.name} is missing mail allow list`, 400); + } + } + await saveSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY, JSON.stringify(settings)); + return c.json({ success: true }) +} + +export default { + getUserOauth2Settings, + saveUserOauth2Settings, +} diff --git a/worker/src/constants.ts b/worker/src/constants.ts index bd815f4c..19cfb71f 100644 --- a/worker/src/constants.ts +++ b/worker/src/constants.ts @@ -6,6 +6,7 @@ export const CONSTANTS = { SEND_BLOCK_LIST_KEY: 'send_block_list', AUTO_CLEANUP_KEY: 'auto_cleanup', USER_SETTINGS_KEY: 'user_settings', + OAUTH2_SETTINGS_KEY: 'oauth2_settings', VERIFIED_ADDRESS_LIST_KEY: 'verified_address_list', // KV diff --git a/worker/src/models/index.ts b/worker/src/models/index.ts index eb2d799f..77cc41a9 100644 --- a/worker/src/models/index.ts +++ b/worker/src/models/index.ts @@ -136,3 +136,19 @@ export class WebhookSettings { "parsedHtml": "${parsedHtml}", }, null, 2) } + +export type UserOauth2Settings = { + name: string; + clientID: string; + clientSecret: string; + authorizationURL: string; + accessTokenURL: string; + accessTokenFormat: string; + userInfoURL: string; + redirectURL: string; + logoutURL?: string; + userEmailKey: string; + scope: string; + enableMailAllowList?: boolean | undefined; + mailAllowList?: string[] | undefined; +} diff --git a/worker/src/user_api/index.ts b/worker/src/user_api/index.ts index fac66c74..ae0281ee 100644 --- a/worker/src/user_api/index.ts +++ b/worker/src/user_api/index.ts @@ -5,6 +5,7 @@ import settings from './settings'; import user from './user'; import bind_address from './bind_address'; import passkey from './passkey'; +import oauth2 from './oauth2'; export const api = new Hono(); @@ -17,6 +18,10 @@ api.post('/user_api/login', user.login); api.post('/user_api/verify_code', user.verifyCode); api.post('/user_api/register', user.register); +// oauth2 api +api.get('/user_api/oauth2/login_url', oauth2.getOauth2LoginUrl); +api.post('/user_api/oauth2/callback', oauth2.oauth2Login); + // bind address api api.get('/user_api/bind_address', bind_address.getBindedAddresses); api.post('/user_api/bind_address', bind_address.bind); diff --git a/worker/src/user_api/oauth2.ts b/worker/src/user_api/oauth2.ts new file mode 100644 index 00000000..d0b0705a --- /dev/null +++ b/worker/src/user_api/oauth2.ts @@ -0,0 +1,105 @@ +import { Context } from 'hono'; +import { Jwt } from 'hono/utils/jwt' + +import { HonoCustomType } from '../types'; +import { getJsonSetting } from '../utils'; +import { UserOauth2Settings } from '../models'; +import { CONSTANTS } from '../constants'; + + +export default { + getOauth2LoginUrl: async (c: Context) => { + const settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY); + const { clientID, state } = c.req.query(); + const setting = settings?.find(s => s.clientID === clientID); + if (!setting) { + return c.text("Client not found", 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 }>(); + if (!clientID || !code) { + return c.text("clientID or code is missing", 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); + } + const params = { + code, + client_id: setting.clientID, + client_secret: setting.clientSecret, + grant_type: 'authorization_code', + } + const res = await fetch(setting.accessTokenURL, { + method: 'POST', + body: setting.accessTokenFormat === 'json' + ? JSON.stringify(params) : + new URLSearchParams(params).toString(), + headers: { + 'Content-Type': setting.accessTokenFormat === 'json' + ? 'application/json' + : 'application/x-www-form-urlencoded', + "Accept": "application/json" + } + }) + 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); + } + const resJson = await res.json(); + const { access_token, token_type } = resJson as { access_token: string, token_type?: string }; + const user = await fetch(setting.userInfoURL, { + headers: { + "Authorization": `${token_type || 'Bearer'} ${access_token}`, + "Accept": "application/json", + "User-Agent": "Cloudflare Workers" + } + }) + 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); + } + 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); + } + // 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) + } + // insert or update user + const { success } = await c.env.DB.prepare( + `INSERT INTO users (user_email, password, user_info)` + + ` VALUES (?, '', ?)` + + ` ON CONFLICT(user_email) DO UPDATE SET updated_at = datetime('now')` + ).bind( + email, JSON.stringify(userInfo) + ).run(); + if (!success) { + return c.text("Failed to register", 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) + } + // create jwt + const jwt = await Jwt.sign({ + user_email: email, + user_id: user_id, + // 30 days expire in seconds + exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, + iat: Math.floor(Date.now() / 1000), + }, c.env.JWT_SECRET, "HS256") + return c.json({ + jwt: jwt + }) + } +} diff --git a/worker/src/user_api/settings.ts b/worker/src/user_api/settings.ts index 0c706fee..47fe0cc4 100644 --- a/worker/src/user_api/settings.ts +++ b/worker/src/user_api/settings.ts @@ -1,7 +1,7 @@ import { Context } from "hono"; import { HonoCustomType } from "../types"; -import { UserSettings } from "../models"; +import { UserOauth2Settings, UserSettings } from "../models"; import { getJsonSetting, getUserRoles } from "../utils" import { CONSTANTS } from "../constants"; import { commonGetUserRole } from "../common"; @@ -11,9 +11,22 @@ export default { openSettings: async (c: Context) => { const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY); const settings = new UserSettings(value); + const oauth2ClientIDs = [] as { clientID: string, name: string }[]; + try { + const oauth2Settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY); + oauth2ClientIDs.push( + ...oauth2Settings?.map(s => ({ + clientID: s.clientID, + name: s.name + })) || [] + ); + } catch (e) { + console.error("Failed to get oauth2 settings", e); + } return c.json({ enable: settings.enable, enableMailVerify: settings.enableMailVerify, + oauth2ClientIDs: oauth2ClientIDs, }) }, settings: async (c: Context) => { diff --git a/worker/src/utils.ts b/worker/src/utils.ts index 950d8a78..0aaf0600 100644 --- a/worker/src/utils.ts +++ b/worker/src/utils.ts @@ -1,17 +1,16 @@ import { Context } from "hono"; import { createMimeMessage } from "mimetext"; import { HonoCustomType, UserRole } from "./types"; -import { User } from "telegraf/types"; -export const getJsonSetting = async ( +export const getJsonSetting = async ( c: Context, key: string -): Promise => { +): Promise => { const value = await getSetting(c, key); if (!value) { return null; } try { - return JSON.parse(value); + return JSON.parse(value) as T; } catch (e) { console.error(`GetJsonSetting: Failed to parse ${key}`, e); } diff --git a/worker/src/worker.ts b/worker/src/worker.ts index a5f5b26f..abd319a0 100644 --- a/worker/src/worker.ts +++ b/worker/src/worker.ts @@ -127,6 +127,7 @@ app.use('/user_api/*', async (c, next) => { || c.req.path.startsWith("/user_api/login") || c.req.path.startsWith("/user_api/verify_code") || c.req.path.startsWith("/user_api/passkey/authenticate_") + || c.req.path.startsWith("/user_api/oauth2") ) { await next(); return;