diff --git a/src/client/api/authjs/useAuth.ts b/src/client/api/authjs/useAuth.ts
index 8723a59..083489b 100644
--- a/src/client/api/authjs/useAuth.ts
+++ b/src/client/api/authjs/useAuth.ts
@@ -4,6 +4,7 @@ import { useUserStore } from '@/store/user';
import { toast } from 'sonner';
import { trpc } from '../trpc';
import { useTranslation } from '@i18next-toolkit/react';
+import { BuiltInProviderType } from '@auth/core/providers';
export function useAuth() {
const trpcUtils = trpc.useUtils();
@@ -38,6 +39,32 @@ export function useAuth() {
}
);
+ const loginWithOAuth = useEvent(async (provider: BuiltInProviderType) => {
+ let res: SignInResponse | undefined;
+ try {
+ res = await signIn(provider, {
+ redirect: false,
+ });
+ console.log('res', res);
+ } catch (err) {
+ toast.error(t('Login failed'));
+ throw err;
+ }
+
+ if (res?.error) {
+ toast.error(t('Login failed'));
+ throw new Error('Login failed');
+ }
+
+ const userInfo = await trpcUtils.user.info.fetch();
+ if (!userInfo) {
+ toast.error(t('Can not get current user info'));
+ throw new Error('Login failed, ');
+ }
+
+ return userInfo;
+ });
+
const logout = useEvent(async () => {
await signOut({
redirect: false,
@@ -49,6 +76,7 @@ export function useAuth() {
return {
loginWithPassword,
+ loginWithOAuth,
logout,
};
}
diff --git a/src/client/hooks/useConfig.ts b/src/client/hooks/useConfig.ts
index d87fdff..f30d38e 100644
--- a/src/client/hooks/useConfig.ts
+++ b/src/client/hooks/useConfig.ts
@@ -6,6 +6,7 @@ const defaultGlobalConfig: AppRouterOutput['global']['config'] = {
allowRegister: false,
alphaMode: false,
disableAnonymousTelemetry: false,
+ authProvider: [],
};
const callAnonymousTelemetry = once(() => {
diff --git a/src/client/routes/login.tsx b/src/client/routes/login.tsx
index cf95203..327d0f7 100644
--- a/src/client/routes/login.tsx
+++ b/src/client/routes/login.tsx
@@ -1,6 +1,6 @@
import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router';
import { useGlobalConfig } from '@/hooks/useConfig';
-import { Form, Typography } from 'antd';
+import { Divider, Form, Typography } from 'antd';
import { useTranslation } from '@i18next-toolkit/react';
import { setUserInfo } from '@/store/user';
import { z } from 'zod';
@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { useAuth } from '@/api/authjs/useAuth';
import { useEventWithLoading } from '@/hooks/useEvent';
+import { LuGithub } from 'react-icons/lu';
export const Route = createFileRoute('/login')({
validateSearch: z.object({
@@ -29,7 +30,7 @@ function LoginComponent() {
const { t } = useTranslation();
const search = Route.useSearch();
- const { loginWithPassword } = useAuth();
+ const { loginWithPassword, loginWithOAuth } = useAuth();
const [handleLogin, loading] = useEventWithLoading(async (values: any) => {
const userInfo = await loginWithPassword(values.username, values.password);
@@ -41,7 +42,7 @@ function LoginComponent() {
replace: true,
});
});
- const { allowRegister } = useGlobalConfig();
+ const { allowRegister, authProvider } = useGlobalConfig();
return (
@@ -96,6 +97,22 @@ function LoginComponent() {
)}
+
+ {authProvider.length > 0 && (
+ <>
+
{t('Or')}
+
+
+
+
+ >
+ )}
);
diff --git a/src/server/model/auth.ts b/src/server/model/auth.ts
index 3fbcc6f..c1bc7cb 100644
--- a/src/server/model/auth.ts
+++ b/src/server/model/auth.ts
@@ -1,6 +1,7 @@
import { Auth, AuthConfig, createActionURL } from '@auth/core';
import Nodemailer from '@auth/core/providers/nodemailer';
import Credentials from '@auth/core/providers/credentials';
+import Github from '@auth/core/providers/github';
import { env } from '../utils/env.js';
import { prisma } from './_client.js';
import type { PrismaClient, Prisma, User } from '@prisma/client';
@@ -28,7 +29,8 @@ export interface UserAuthPayload {
export const authConfig: Omit = {
debug: env.isProd ? false : true,
basePath: '/api/auth',
- providers: [
+ trustHost: true,
+ providers: _.compact([
Credentials({
id: 'account',
name: 'Account',
@@ -52,28 +54,41 @@ export const authConfig: Omit = {
return toAdapterUser(user);
},
}),
- Nodemailer({
- id: 'email',
- name: 'Email',
- ...env.auth.email,
- async sendVerificationRequest(params) {
- const { identifier, url, provider, theme } = params;
- const { host } = new URL(url);
- const transport = createTransport(provider.server);
- const result = await transport.sendMail({
- to: identifier,
- from: provider.from,
- subject: `Sign in Tianji to ${host}`,
- text: `Sign in Tianji to ${host}\n${url}\n\n`,
- html: nodemailHtmlBody({ url, host, theme }),
- });
- const failed = result.rejected.concat(result.pending).filter(Boolean);
- if (failed.length) {
- throw new Error(`Email (${failed.join(', ')}) could not be sent`);
- }
- },
- }),
- ],
+ env.auth.provider.includes('email') &&
+ Nodemailer({
+ id: 'email',
+ name: 'Email',
+ ...env.auth.email,
+ async sendVerificationRequest(params) {
+ const { identifier, url, provider, theme } = params;
+ const { host } = new URL(url);
+ const transport = createTransport(provider.server);
+ const result = await transport.sendMail({
+ to: identifier,
+ from: provider.from,
+ subject: `Sign in Tianji to ${host}`,
+ text: `Sign in Tianji to ${host}\n${url}\n\n`,
+ html: nodemailHtmlBody({ url, host, theme }),
+ });
+ const failed = result.rejected.concat(result.pending).filter(Boolean);
+ if (failed.length) {
+ throw new Error(`Email (${failed.join(', ')}) could not be sent`);
+ }
+ },
+ }),
+ env.auth.provider.includes('github') &&
+ Github({
+ id: 'github',
+ name: 'Github',
+ ...env.auth.github,
+ }),
+ env.auth.provider.includes('google') &&
+ Github({
+ id: 'google',
+ name: 'Google',
+ ...env.auth.google,
+ }),
+ ]),
adapter: TianjiPrismaAdapter(prisma),
secret: env.auth.secret,
session: {
diff --git a/src/server/trpc/routers/global.ts b/src/server/trpc/routers/global.ts
index d03b936..7289121 100644
--- a/src/server/trpc/routers/global.ts
+++ b/src/server/trpc/routers/global.ts
@@ -23,6 +23,7 @@ export const globalRouter = router({
alphaMode: z.boolean(),
disableAnonymousTelemetry: z.boolean(),
customTrackerScriptName: z.string().optional(),
+ authProvider: z.array(z.string()),
})
)
.query(async ({ input }) => {
@@ -34,6 +35,7 @@ export const globalRouter = router({
alphaMode: env.alphaMode,
disableAnonymousTelemetry: env.disableAnonymousTelemetry,
customTrackerScriptName: env.customTrackerScriptName,
+ authProvider: env.auth.provider,
};
}),
});
diff --git a/src/server/utils/env.ts b/src/server/utils/env.ts
index 2386cee..ca0db57 100644
--- a/src/server/utils/env.ts
+++ b/src/server/utils/env.ts
@@ -1,5 +1,6 @@
import { v1 as uuid } from 'uuid';
import md5 from 'md5';
+import _ from 'lodash';
const jwtSecret =
!process.env.JWT_SECRET ||
@@ -13,11 +14,24 @@ export const env = {
jwtSecret,
port: Number(process.env.PORT || 12345),
auth: {
+ provider: _.compact([
+ !!process.env.EMAIL_SERVER && 'email',
+ !!process.env.AUTH_GITHUB_ID && 'github',
+ !!process.env.AUTH_GOOGLE_ID && 'google',
+ ]),
secret: process.env.AUTH_SECRET || md5(jwtSecret),
email: {
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
},
+ github: {
+ clientId: process.env.AUTH_GITHUB_ID,
+ clientSecret: process.env.AUTH_GITHUB_SECRET,
+ },
+ google: {
+ clientId: process.env.AUTH_GOOGLE_ID,
+ clientSecret: process.env.AUTH_GOOGLE_SECRET,
+ },
},
allowRegister: checkEnvTrusty(process.env.ALLOW_REGISTER),
allowOpenapi: checkEnvTrusty(process.env.ALLOW_OPENAPI ?? 'true'),