Skip to content

Commit

Permalink
feat: login / logout with oidc
Browse files Browse the repository at this point in the history
  • Loading branch information
Vsion committed Jan 19, 2024
1 parent 804cf47 commit ef59e29
Show file tree
Hide file tree
Showing 24 changed files with 322 additions and 65 deletions.
11 changes: 11 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

import config from './src/config/oidc.mjs';
const oidcUrl = config.server.url;
const isProd = process.env.NODE_ENV === 'production';

const nextConfig = {
Expand All @@ -24,6 +26,15 @@ const nextConfig = {
},
reactStrictMode: true,
transpilePackages: ['antd', '@ant-design', 'antd-style', '@lobehub/ui', 'antd-mobile'],
// async rewrites() {
// return [
// {
// source: '/oidc/token',
// destination: `${oidcUrl}/oidc/token`,
// secure: false,
// },
// ]
// },
webpack: (config) => {
config.experiments = {
asyncWebAssembly: true,
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"version": "0.1.0",
"scripts": {
"build": "next build",
"dev": "next dev",
"dev": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 next dev",
"dev-https": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 next dev --experimental-https --experimental-https-key ./cert/key.pem --experimental-https-cert ./cert/cert.pem",
"lint": "npm run lint:es && npm run lint:style",
"lint-fix": "npm run lint-fix:es && npm run lint-fix:style",
"lint-fix:es": "eslint --ext .jsx,.js,.tsx,.ts src --fix",
Expand Down Expand Up @@ -50,6 +51,7 @@
"classnames": "^2.5.1",
"lucide-react": "^0.304.0",
"next": "14.0.4",
"query-string": "^8.1.0",
"react": "^18",
"react-dom": "^18",
"react-layout-kit": "^1.7.4",
Expand All @@ -60,11 +62,13 @@
"devDependencies": {
"@types/mdx": "^2.0.10",
"@types/node": "^20",
"@types/query-string": "^6.3.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/ua-parser-js": "^0.7.39",
"@yuntijs/lint": "^1.4.0",
"commitlint": "^18.4.3",
"cross-env": "^7.0.3",
"eslint": "^8.56.0",
"eslint-config-next": "14.0.4",
"husky": "^8.0.3",
Expand Down
34 changes: 27 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions src/app/actions/user.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use server';

type User = {
name: string;
full_name: string;
};
import { User } from '@/types/user';

export async function getUserData() {
// Fetch data from external API
// todo
// 1. use api
// 2. error handle

const res = await fetch(`https://api.github.com/repos/kubeagi/agent-portal`, {
// cache: 'no-store',
// cache: 'no-store', // 每次都请求动态数据
next: {
revalidate: 5,
revalidate: 5, // 缓存
},
});
const data: any = await res.json();
Expand Down
10 changes: 1 addition & 9 deletions src/app/chat/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1 @@
import { PropsWithChildren, memo } from 'react';

import AppLayout from '@/layout/AppLayout';

const AppLayoutDesktop = memo<PropsWithChildren>(({ children }) => {
return <AppLayout>{children}</AppLayout>;
});

export default AppLayoutDesktop;
export { default } from '@/layout/AppLayoutWithUser';
31 changes: 31 additions & 0 deletions src/app/oidc/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use client';

import { useRouter } from 'next/navigation';
import queryString from 'query-string';
import React from 'react';

import oidc from '@/config/oidc.mjs';

const { client, server, AUTH_DATA } = oidc;
const { url } = server;
const { client_id, redirect_uri } = client;

export default function Auth() {
const router = useRouter();
React.useEffect(() => {
const authData = localStorage.getItem(AUTH_DATA);
if (authData) {
// todo validate auth
router.push('/chat');
return;
}
const query = queryString.stringify({
client_id,
redirect_uri: `${window.location.origin}${redirect_uri}`,
response_type: 'code',
scope: 'openid profile email groups offline_access',
});
window.location.href = `${url}/oidc/auth?${query}`;
}, []);
return <> </>;
}
41 changes: 41 additions & 0 deletions src/app/oidc/callback/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client';

import { useRouter, useSearchParams } from 'next/navigation';
import React from 'react';
import { useDispatch } from 'react-redux';

import oidc from '@/config/oidc.mjs';

const { client, AUTH_DATA } = oidc;
const { redirect_uri } = client;

export default function Auth() {
const searchParams = useSearchParams();
const dispatch = useDispatch();
const router = useRouter();
const code = searchParams.get('code');
const fetchAuth = async () => {
fetch(`/oidc/token?code=${code}&redirect_uri=${location.origin}${redirect_uri}`, {
method: 'POST',
})
.then(res => res.json())
.then(res => {
if (res.data?.errors) {
console.warn(res.data?.errors);
return;
}
if (res.data) {
localStorage.setItem(AUTH_DATA, JSON.stringify(res.data));
dispatch({
type: 'SAVE_AUTH_DATA',
authData: res.data,
});
router.push('/chat');
}
});
};
React.useEffect(() => {
fetchAuth();
}, []);
return <> </>;
}
38 changes: 38 additions & 0 deletions src/app/oidc/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import { Flex, Spin } from 'antd';
import { usePathname } from 'next/navigation';
import React from 'react';

export default function Layout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const tip = React.useMemo(() => {
switch (pathname) {
case '/oidc/logout': {
return '登出中...';
}
default: {
return '加载中...';
}
}
}, [pathname]);
return (
<Flex
align={'center'}
justify={'center'}
style={{
height: '100vh',
}}
>
<Spin
spinning
style={{
minWidth: '60px',
}}
tip={<span>{tip}</span>}
>
{children}
</Spin>
</Flex>
);
}
19 changes: 19 additions & 0 deletions src/app/oidc/logout/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client';

import React from 'react';

import oidc from '@/config/oidc.mjs';

const { client, AUTH_DATA, server } = oidc;
const { redirect_uri } = client;
const { url } = server;

export default function Auth() {
React.useEffect(() => {
localStorage.removeItem(AUTH_DATA);
window.location.href = `${url}/oidc/logout/remove-auth-data?redirect=${encodeURIComponent(
`${url}/oidc/auth?redirect_uri=${location.origin}${redirect_uri}&response_type=code&scope=openid+profile+email+groups+offline_access`
)}`;
}, []);
return <> </>;
}
7 changes: 7 additions & 0 deletions src/app/oidc/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import React from 'react';

export default function oidc() {
return <>oidc</>;
}
43 changes: 43 additions & 0 deletions src/app/oidc/token/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { type NextRequest, NextResponse } from 'next/server';

import oidc from '@/config/oidc.mjs';
import { btoa } from '@/utils';

const { client, server } = oidc;
const { url } = server;
const { client_id, client_secret } = client;

function fetchWithTimeout(url: string, options: RequestInit, timeout = 3000) {
const fetchPromise = fetch(url, options);
const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Request timed out'));
}, timeout);
});
return Promise.race([fetchPromise, timeoutPromise]);
}

export async function POST(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const code = searchParams.get('code');
const redirect_uri = searchParams.get('redirect_uri');
const body = {
grant_type: 'authorization_code',
code,
redirect_uri,
};
const res: any = await fetchWithTimeout(
`${url}/oidc/token`,
{
method: 'POST',
headers: {
'Authorization': `Basic ${btoa(`${client_id}:${client_secret}`)}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
},
10_000
);
const data = await res.json();
return NextResponse.json({ data });
}
Loading

0 comments on commit ef59e29

Please sign in to comment.