Merge branch 'main' of http://113.45.67.59:3003/insiinc/nice
This commit is contained in:
commit
54c8fa6bf2
|
@ -0,0 +1,21 @@
|
||||||
|
TIMEZONE=Asia/Singapore
|
||||||
|
|
||||||
|
# PostgreSQL 配置
|
||||||
|
POSTGRES_VERSION=latest
|
||||||
|
|
||||||
|
# PostgreSQL Env
|
||||||
|
POSTGRES_DB=nice
|
||||||
|
POSTGRES_USER=nice
|
||||||
|
POSTGRES_PASSWORD=nice
|
||||||
|
|
||||||
|
# Redis 配置
|
||||||
|
REDIS_VERSION=7.2.4
|
||||||
|
REDIS_PASSWORD=nice
|
||||||
|
|
||||||
|
# MinIO 配置
|
||||||
|
MINIO_VERSION=latest
|
||||||
|
MINIO_ACCESS_KEY=nice
|
||||||
|
MINIO_SECRET_KEY=nice123
|
||||||
|
# Elasticsearch 配置
|
||||||
|
ELASTIC_VERSION=9.0.1
|
||||||
|
ELASTIC_PASSWORD=nice_elastic_password
|
|
@ -37,3 +37,4 @@ npm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
packages/db/generated
|
packages/db/generated
|
||||||
|
volumes
|
|
@ -12,6 +12,7 @@
|
||||||
"@types/oidc-provider": "^9.1.0",
|
"@types/oidc-provider": "^9.1.0",
|
||||||
"hono": "^4.7.10",
|
"hono": "^4.7.10",
|
||||||
"ioredis": "5.4.1",
|
"ioredis": "5.4.1",
|
||||||
|
"jose": "^6.0.11",
|
||||||
"minio": "7.1.3",
|
"minio": "7.1.3",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"node-cron": "^4.0.7",
|
"node-cron": "^4.0.7",
|
||||||
|
|
|
@ -44,6 +44,7 @@ app.get('/', (c) => {
|
||||||
app.use('/oidc/*', async (c, next) => {
|
app.use('/oidc/*', async (c, next) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await oidc.callback(c.req.raw, c.res.raw)
|
await oidc.callback(c.req.raw, c.res.raw)
|
||||||
return c.finalize()
|
// return void 也可以
|
||||||
|
return
|
||||||
})
|
})
|
||||||
export default app
|
export default app
|
||||||
|
|
|
@ -1,37 +1,108 @@
|
||||||
import { Configuration } from 'oidc-provider';
|
import { Configuration } from 'oidc-provider';
|
||||||
import { nanoid } from 'nanoid';
|
import { RedisAdapter } from './redis-adapter';
|
||||||
|
import { prisma } from '@repo/db';
|
||||||
|
|
||||||
|
async function getClients() {
|
||||||
|
const dbClients = await prisma.oidcClient.findMany?.();
|
||||||
|
const dbClientList = (dbClients && dbClients.length > 0)
|
||||||
|
? dbClients.map(c => ({
|
||||||
|
client_id: c.clientId,
|
||||||
|
client_secret: c.clientSecret,
|
||||||
|
grant_types: JSON.parse(c.grantTypes), // string -> string[]
|
||||||
|
redirect_uris: JSON.parse(c.redirectUris), // string -> string[]
|
||||||
|
response_types: JSON.parse(c.responseTypes), // string -> string[]
|
||||||
|
scope: c.scope,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// 管理后台client,通过环境变量读取
|
||||||
|
const defaultClient = {
|
||||||
|
client_id: process.env.OIDC_CLIENT_ID || 'admin-client',
|
||||||
|
client_secret: process.env.OIDC_CLIENT_SECRET || 'admin-secret',
|
||||||
|
grant_types: ['authorization_code', 'refresh_token'],
|
||||||
|
redirect_uris: [process.env.OIDC_REDIRECT_URI || 'http://localhost:3000/callback'],
|
||||||
|
response_types: ['code'],
|
||||||
|
scope: 'openid email profile',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否与数据库client_id重复
|
||||||
|
const allClients = [defaultClient, ...dbClientList.filter(c => c.client_id !== defaultClient.client_id)];
|
||||||
|
|
||||||
|
return allClients;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OIDC_COOKIE_KEY = process.env.OIDC_COOKIE_KEY || 'HrbEPlzByV0CcjFJhl2pjKV2iG8FgQIc';
|
||||||
|
|
||||||
const config: Configuration = {
|
const config: Configuration = {
|
||||||
clients: [
|
adapter: RedisAdapter,
|
||||||
{
|
// 注意:clients字段现在是Promise,需在Provider初始化时await
|
||||||
client_id: 'example-client',
|
clients: await getClients(),
|
||||||
client_secret: 'example-secret',
|
|
||||||
grant_types: ['authorization_code', 'refresh_token'],
|
|
||||||
redirect_uris: ['http://localhost:3000/callback'],
|
|
||||||
response_types: ['code'],
|
|
||||||
scope: 'openid email profile',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
pkce: {
|
pkce: {
|
||||||
required: () => true, // 要求所有客户端使用PKCE
|
required: () => true,
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
devInteractions: { enabled: false }, // 禁用开发交互界面
|
devInteractions: { enabled: false },
|
||||||
resourceIndicators: { enabled: true }, // 启用资源指示器
|
resourceIndicators: { enabled: true },
|
||||||
revocation: { enabled: true }, // 启用令牌撤销
|
revocation: { enabled: true },
|
||||||
userinfo: { enabled: true }, // 启用用户信息端点
|
userinfo: { enabled: true },
|
||||||
|
registration: { enabled: true },
|
||||||
},
|
},
|
||||||
cookies: {
|
cookies: {
|
||||||
keys: [nanoid()], // 用于签署和验证cookie
|
keys: [OIDC_COOKIE_KEY],
|
||||||
},
|
},
|
||||||
jwks: {
|
jwks: {
|
||||||
keys: [], // 在实际环境中应该生成并保存密钥
|
keys: [
|
||||||
|
{
|
||||||
|
d: 'VEZOsY07JTFzGTqv6cC2Y32vsfChind2I_TTuvV225_-0zrSej3XLRg8iE_u0-3GSgiGi4WImmTwmEgLo4Qp3uEcxCYbt4NMJC7fwT2i3dfRZjtZ4yJwFl0SIj8TgfQ8ptwZbFZUlcHGXZIr4nL8GXyQT0CK8wy4COfmymHrrUoyfZA154ql_OsoiupSUCRcKVvZj2JHL2KILsq_sh_l7g2dqAN8D7jYfJ58MkqlknBMa2-zi5I0-1JUOwztVNml_zGrp27UbEU60RqV3GHjoqwI6m01U7K0a8Q_SQAKYGqgepbAYOA-P4_TLl5KC4-WWBZu_rVfwgSENwWNEhw8oQ',
|
||||||
|
dp: 'E1Y-SN4bQqX7kP-bNgZ_gEv-pixJ5F_EGocHKfS56jtzRqQdTurrk4jIVpI-ZITA88lWAHxjD-OaoJUh9Jupd_lwD5Si80PyVxOMI2xaGQiF0lbKJfD38Sh8frRpgelZVaK_gm834B6SLfxKdNsP04DsJqGKktODF_fZeaGFPH0',
|
||||||
|
dq: 'F90JPxevQYOlAgEH0TUt1-3_hyxY6cfPRU2HQBaahyWrtCWpaOzenKZnvGFZdg-BuLVKjCchq3G_70OLE-XDP_ol0UTJmDTT-WyuJQdEMpt_WFF9yJGoeIu8yohfeLatU-67ukjghJ0s9CBzNE_LrGEV6Cup3FXywpSYZAV3iqc',
|
||||||
|
e: 'AQAB',
|
||||||
|
kty: 'RSA',
|
||||||
|
n: 'xwQ72P9z9OYshiQ-ntDYaPnnfwG6u9JAdLMZ5o0dmjlcyrvwQRdoFIKPnO65Q8mh6F_LDSxjxa2Yzo_wdjhbPZLjfUJXgCzm54cClXzT5twzo7lzoAfaJlkTsoZc2HFWqmcri0BuzmTFLZx2Q7wYBm0pXHmQKF0V-C1O6NWfd4mfBhbM-I1tHYSpAMgarSm22WDMDx-WWI7TEzy2QhaBVaENW9BKaKkJklocAZCxk18WhR0fckIGiWiSM5FcU1PY2jfGsTmX505Ub7P5Dz75Ygqrutd5tFrcqyPAtPTFDk8X1InxkkUwpP3nFU5o50DGhwQolGYKPGtQ-ZtmbOfcWQ',
|
||||||
|
p: '5wC6nY6Ev5FqcLPCqn9fC6R9KUuBej6NaAVOKW7GXiOJAq2WrileGKfMc9kIny20zW3uWkRLm-O-3Yzze1zFpxmqvsvCxZ5ERVZ6leiNXSu3tez71ZZwp0O9gys4knjrI-9w46l_vFuRtjL6XEeFfHEZFaNJpz-lcnb3w0okrbM',
|
||||||
|
q: '3I1qeEDslZFB8iNfpKAdWtz_Wzm6-jayT_V6aIvhvMj5mnU-Xpj75zLPQSGa9wunMlOoZW9w1wDO1FVuDhwzeOJaTm-Ds0MezeC4U6nVGyyDHb4CUA3ml2tzt4yLrqGYMT7XbADSvuWYADHw79OFjEi4T3s3tJymhaBvy1ulv8M',
|
||||||
|
qi: 'wSbXte9PcPtr788e713KHQ4waE26CzoXx-JNOgN0iqJMN6C4_XJEX-cSvCZDf4rh7xpXN6SGLVd5ibIyDJi7bbi5EQ5AXjazPbLBjRthcGXsIuZ3AtQyR0CEWNSdM7EyM5TRdyZQ9kftfz9nI03guW3iKKASETqX2vh0Z8XRjyU',
|
||||||
|
use: 'sig',
|
||||||
|
}, {
|
||||||
|
crv: 'P-256',
|
||||||
|
d: 'K9xfPv773dZR22TVUB80xouzdF7qCg5cWjPjkHyv7Ws',
|
||||||
|
kty: 'EC',
|
||||||
|
use: 'sig',
|
||||||
|
x: 'FWZ9rSkLt6Dx9E3pxLybhdM6xgR5obGsj5_pqmnz5J4',
|
||||||
|
y: '_n8G69C-A2Xl4xUW2lF0i8ZGZnk_KPYrhv4GbTGu5G4',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
ttl: {
|
ttl: {
|
||||||
AccessToken: 3600, // 1小时
|
AccessToken: 3600,
|
||||||
AuthorizationCode: 600, // 10分钟
|
AuthorizationCode: 600,
|
||||||
IdToken: 3600, // 1小时
|
IdToken: 3600,
|
||||||
RefreshToken: 1209600, // 14天
|
RefreshToken: 1209600,
|
||||||
|
BackchannelAuthenticationRequest: 600,
|
||||||
|
ClientCredentials: 600,
|
||||||
|
DeviceCode: 600,
|
||||||
|
Grant: 1209600,
|
||||||
|
Interaction: 3600,
|
||||||
|
Session: 1209600,
|
||||||
|
RegistrationAccessToken: 3600,
|
||||||
|
DPoPProof: 300,
|
||||||
|
PushedAuthorizationRequest: 600,
|
||||||
|
ReplayDetection: 3600,
|
||||||
|
LogoutToken: 600,
|
||||||
|
},
|
||||||
|
findAccount: async (ctx, id) => {
|
||||||
|
const user = await prisma.user.findUnique({ where: { id } });
|
||||||
|
if (!user) return undefined;
|
||||||
|
return {
|
||||||
|
accountId: user.id,
|
||||||
|
async claims() {
|
||||||
|
return {
|
||||||
|
sub: user.id,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Provider } from 'oidc-provider';
|
import { Provider } from 'oidc-provider';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
|
|
||||||
const oidc = new Provider('http://localhost:4000', config);
|
const oidc = new Provider('http://localhost:3000', config);
|
||||||
|
|
||||||
export default oidc;
|
export default oidc;
|
|
@ -0,0 +1,86 @@
|
||||||
|
import type { Adapter, AdapterPayload } from 'oidc-provider';
|
||||||
|
import redis from '../redis';
|
||||||
|
|
||||||
|
export class RedisAdapter implements Adapter {
|
||||||
|
name: string;
|
||||||
|
constructor(name: string) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
key(id: string) {
|
||||||
|
return `${this.name}:${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsert(id: string, payload: AdapterPayload, expiresIn: number) {
|
||||||
|
const key = this.key(id);
|
||||||
|
await redis.set(key, JSON.stringify(payload), 'EX', expiresIn);
|
||||||
|
if (payload && payload.grantId) {
|
||||||
|
// 记录grantId到id的映射,便于revokeByGrantId
|
||||||
|
await redis.sadd(this.grantKey(payload.grantId), id);
|
||||||
|
await redis.expire(this.grantKey(payload.grantId), expiresIn);
|
||||||
|
}
|
||||||
|
if (payload && payload.userCode) {
|
||||||
|
await redis.set(this.userCodeKey(payload.userCode), id, 'EX', expiresIn);
|
||||||
|
}
|
||||||
|
if (payload && payload.uid) {
|
||||||
|
await redis.set(this.uidKey(payload.uid), id, 'EX', expiresIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async find(id: string) {
|
||||||
|
const data = await redis.get(this.key(id));
|
||||||
|
return data ? JSON.parse(data) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByUserCode(userCode: string) {
|
||||||
|
const id = await redis.get(this.userCodeKey(userCode));
|
||||||
|
return id ? this.find(id) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByUid(uid: string) {
|
||||||
|
const id = await redis.get(this.uidKey(uid));
|
||||||
|
return id ? this.find(id) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy(id: string) {
|
||||||
|
const data = await this.find(id);
|
||||||
|
await redis.del(this.key(id));
|
||||||
|
if (data && data.grantId) {
|
||||||
|
await redis.srem(this.grantKey(data.grantId), id);
|
||||||
|
}
|
||||||
|
if (data && data.userCode) {
|
||||||
|
await redis.del(this.userCodeKey(data.userCode));
|
||||||
|
}
|
||||||
|
if (data && data.uid) {
|
||||||
|
await redis.del(this.uidKey(data.uid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async revokeByGrantId(grantId: string) {
|
||||||
|
const key = this.grantKey(grantId);
|
||||||
|
const ids = await redis.smembers(key);
|
||||||
|
if (ids && ids.length) {
|
||||||
|
await Promise.all(ids.map((id) => this.destroy(id)));
|
||||||
|
}
|
||||||
|
await redis.del(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async consume(id: string) {
|
||||||
|
const key = this.key(id);
|
||||||
|
const data = await this.find(id);
|
||||||
|
if (data) {
|
||||||
|
data.consumed = Math.floor(Date.now() / 1000);
|
||||||
|
await redis.set(key, JSON.stringify(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grantKey(grantId: string) {
|
||||||
|
return `${this.name}:grant:${grantId}`;
|
||||||
|
}
|
||||||
|
userCodeKey(userCode: string) {
|
||||||
|
return `${this.name}:userCode:${userCode}`;
|
||||||
|
}
|
||||||
|
uidKey(uid: string) {
|
||||||
|
return `${this.name}:uid:${uid}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import Redis from 'ioredis';
|
||||||
const redis = new Redis({
|
const redis = new Redis({
|
||||||
host: 'localhost', // 根据实际情况配置
|
host: 'localhost', // 根据实际情况配置
|
||||||
port: 6379,
|
port: 6379,
|
||||||
// password: 'yourpassword', // 如有需要
|
password: process.env.REDIS_PASSWORD
|
||||||
});
|
});
|
||||||
|
|
||||||
export default redis;
|
export default redis;
|
|
@ -1,5 +1,6 @@
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { initTRPC } from '@trpc/server'
|
import { initTRPC } from '@trpc/server'
|
||||||
|
import { userRouter } from './user/user.trpc'
|
||||||
|
|
||||||
const t = initTRPC.create()
|
const t = initTRPC.create()
|
||||||
|
|
||||||
|
@ -7,9 +8,7 @@ export const publicProcedure = t.procedure
|
||||||
export const router = t.router
|
export const router = t.router
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
hello: publicProcedure.input(z.string().nullish()).query(({ input }) => {
|
user: userRouter
|
||||||
return `Hello ${input ?? 'World'}!`
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter
|
export type AppRouter = typeof appRouter
|
|
@ -1,17 +1,17 @@
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { createUser, searchUser } from "./userindex";
|
import { createUser, searchUser } from "./user.index";
|
||||||
|
|
||||||
const userRoute = new Hono();
|
const userRest = new Hono();
|
||||||
|
|
||||||
userRoute.post('/', async (c) => {
|
userRest.post('/', async (c) => {
|
||||||
const user = await c.req.json();
|
const user = await c.req.json();
|
||||||
const result = await createUser(user);
|
const result = await createUser(user);
|
||||||
return c.json(result);
|
return c.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
userRoute.get('/search', async (c) => {
|
userRest.get('/search', async (c) => {
|
||||||
const q = c.req.query('q') || '';
|
const q = c.req.query('q') || '';
|
||||||
const result = await searchUser(q);
|
const result = await searchUser(q);
|
||||||
return c.json(result.hits.hits);
|
return c.json(result.hits.hits);
|
||||||
});
|
});
|
||||||
export default userRoute;
|
export default userRest;
|
|
@ -1,46 +1,47 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { api } from '@repo/client';
|
// import { api } from '@repo/client';
|
||||||
import { Button } from '@repo/ui/components/button';
|
// import { Button } from '@repo/ui/components/button';
|
||||||
import { useState } from 'react';
|
// import { useState } from 'react';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
// import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { httpBatchLink } from '@trpc/client';
|
// import { httpBatchLink } from '@trpc/client';
|
||||||
|
|
||||||
function HomeContent() {
|
// function HomeContent() {
|
||||||
const [name, setName] = useState('');
|
// const [name, setName] = useState('');
|
||||||
const helloQuery = api.hello.useQuery(name || undefined);
|
// const helloQuery = api.hello.useQuery(name || undefined);
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<div className="p-4">
|
// <div className="p-4">
|
||||||
<input
|
// <input
|
||||||
type="text"
|
// type="text"
|
||||||
value={name}
|
// value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
// onChange={(e) => setName(e.target.value)}
|
||||||
placeholder="输入名字"
|
// placeholder="输入名字"
|
||||||
className="border p-2 mr-2"
|
// className="border p-2 mr-2"
|
||||||
/>
|
// />
|
||||||
<Button onClick={() => helloQuery.refetch()}>{helloQuery.isLoading ? '加载中...' : helloQuery.data}</Button>
|
// <Button onClick={() => helloQuery.refetch()}>{helloQuery.isLoading ? '加载中...' : helloQuery.data}</Button>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [queryClient] = useState(() => new QueryClient());
|
// const [queryClient] = useState(() => new QueryClient());
|
||||||
const [trpcClient] = useState(() =>
|
// const [trpcClient] = useState(() =>
|
||||||
api.createClient({
|
// api.createClient({
|
||||||
links: [
|
// links: [
|
||||||
httpBatchLink({
|
// httpBatchLink({
|
||||||
url: 'http://localhost:3000/api/trpc',
|
// url: 'http://localhost:3000/api/trpc',
|
||||||
}),
|
// }),
|
||||||
],
|
// ],
|
||||||
}),
|
// }),
|
||||||
);
|
// );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<div>123</div>
|
||||||
<api.Provider client={trpcClient} queryClient={queryClient}>
|
// <QueryClientProvider client={queryClient}>
|
||||||
<HomeContent />
|
// <api.Provider client={trpcClient} queryClient={queryClient}>
|
||||||
</api.Provider>
|
// <HomeContent />
|
||||||
</QueryClientProvider>
|
// </api.Provider>
|
||||||
|
// </QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default function QueryProvider({ children }) {
|
||||||
const accessToken = '';
|
const accessToken = '';
|
||||||
|
|
||||||
// 使用Next.js环境变量
|
// 使用Next.js环境变量
|
||||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
// Set the default query options including staleTime.
|
// Set the default query options including staleTime.
|
||||||
const [queryClient] = useState(
|
const [queryClient] = useState(
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `active` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `client_id` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `client_name` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `client_secret` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `client_uri` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `contacts` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `created_by` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `created_time` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `grant_types` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `jwks` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `jwks_uri` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `last_modified_time` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `logo_uri` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `policy_uri` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `post_logout_redirect_uris` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `redirect_uris` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `require_pkce` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `response_types` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `token_endpoint_auth_method` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `tos_uri` on the `oidc_clients` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the `oidc_authorization_codes` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `oidc_consents` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `oidc_key_pairs` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `oidc_sessions` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `oidc_tokens` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- A unique constraint covering the columns `[clientId]` on the table `oidc_clients` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- Added the required column `clientId` to the `oidc_clients` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `clientSecret` to the `oidc_clients` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `grantTypes` to the `oidc_clients` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `redirectUris` to the `oidc_clients` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `responseTypes` to the `oidc_clients` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `updatedAt` to the `oidc_clients` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "oidc_authorization_codes" DROP CONSTRAINT "oidc_authorization_codes_client_id_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "oidc_consents" DROP CONSTRAINT "oidc_consents_client_id_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "oidc_tokens" DROP CONSTRAINT "oidc_tokens_client_id_fkey";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "oidc_clients_client_id_key";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "oidc_clients" DROP COLUMN "active",
|
||||||
|
DROP COLUMN "client_id",
|
||||||
|
DROP COLUMN "client_name",
|
||||||
|
DROP COLUMN "client_secret",
|
||||||
|
DROP COLUMN "client_uri",
|
||||||
|
DROP COLUMN "contacts",
|
||||||
|
DROP COLUMN "created_by",
|
||||||
|
DROP COLUMN "created_time",
|
||||||
|
DROP COLUMN "grant_types",
|
||||||
|
DROP COLUMN "jwks",
|
||||||
|
DROP COLUMN "jwks_uri",
|
||||||
|
DROP COLUMN "last_modified_time",
|
||||||
|
DROP COLUMN "logo_uri",
|
||||||
|
DROP COLUMN "policy_uri",
|
||||||
|
DROP COLUMN "post_logout_redirect_uris",
|
||||||
|
DROP COLUMN "redirect_uris",
|
||||||
|
DROP COLUMN "require_pkce",
|
||||||
|
DROP COLUMN "response_types",
|
||||||
|
DROP COLUMN "token_endpoint_auth_method",
|
||||||
|
DROP COLUMN "tos_uri",
|
||||||
|
ADD COLUMN "clientId" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "clientSecret" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "grantTypes" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "redirectUris" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "responseTypes" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "oidc_authorization_codes";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "oidc_consents";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "oidc_key_pairs";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "oidc_sessions";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "oidc_tokens";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "oidc_clients_clientId_key" ON "oidc_clients"("clientId");
|
|
@ -1,3 +1,3 @@
|
||||||
# Please do not edit this file manually
|
# Please do not edit this file manually
|
||||||
# It should be added in your version-control system (i.e. Git)
|
# It should be added in your version-control system (e.g., Git)
|
||||||
provider = "postgresql"
|
provider = "postgresql"
|
|
@ -99,120 +99,16 @@ model UserLastVisit {
|
||||||
@@map("user_last_visit")
|
@@map("user_last_visit")
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDC 客户端相关模型
|
|
||||||
model OidcClient {
|
model OidcClient {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
clientId String @unique @map("client_id")
|
clientId String @unique
|
||||||
clientSecret String? @map("client_secret")
|
clientSecret String
|
||||||
clientName String @map("client_name")
|
redirectUris String // 存储为JSON字符串
|
||||||
clientUri String? @map("client_uri")
|
grantTypes String // 存储为JSON字符串
|
||||||
logoUri String? @map("logo_uri")
|
responseTypes String // 存储为JSON字符串
|
||||||
contacts String[]
|
scope String
|
||||||
redirectUris String[] @map("redirect_uris")
|
createdAt DateTime @default(now())
|
||||||
postLogoutRedirectUris String[] @map("post_logout_redirect_uris")
|
updatedAt DateTime @updatedAt
|
||||||
tokenEndpointAuthMethod String @map("token_endpoint_auth_method")
|
|
||||||
grantTypes String[] @map("grant_types")
|
|
||||||
responseTypes String[] @map("response_types")
|
|
||||||
scope String
|
|
||||||
jwksUri String? @map("jwks_uri")
|
|
||||||
jwks String?
|
|
||||||
policyUri String? @map("policy_uri")
|
|
||||||
tosUri String? @map("tos_uri")
|
|
||||||
requirePkce Boolean @default(false) @map("require_pkce")
|
|
||||||
active Boolean @default(true)
|
|
||||||
createdBy String? @map("created_by")
|
|
||||||
createdTime DateTime @default(now()) @map("created_time")
|
|
||||||
lastModifiedTime DateTime? @updatedAt @map("last_modified_time")
|
|
||||||
|
|
||||||
// 关联模型
|
|
||||||
consents OidcConsent[]
|
|
||||||
authorizationCodes OidcCode[]
|
|
||||||
tokens OidcToken[]
|
|
||||||
|
|
||||||
@@map("oidc_clients")
|
@@map("oidc_clients")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户同意记录
|
|
||||||
model OidcConsent {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
userId String @map("user_id")
|
|
||||||
clientId String @map("client_id")
|
|
||||||
scope String
|
|
||||||
createdTime DateTime @default(now()) @map("created_time")
|
|
||||||
expiresAt DateTime? @map("expires_at")
|
|
||||||
|
|
||||||
// 关联
|
|
||||||
client OidcClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@unique([userId, clientId])
|
|
||||||
@@map("oidc_consents")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 授权码
|
|
||||||
model OidcCode {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
code String @unique
|
|
||||||
userId String @map("user_id")
|
|
||||||
clientId String @map("client_id")
|
|
||||||
scope String
|
|
||||||
redirectUri String @map("redirect_uri")
|
|
||||||
codeChallenge String? @map("code_challenge")
|
|
||||||
codeChallengeMethod String? @map("code_challenge_method")
|
|
||||||
nonce String?
|
|
||||||
authTime DateTime @default(now()) @map("auth_time")
|
|
||||||
expiresAt DateTime @map("expires_at")
|
|
||||||
used Boolean @default(false)
|
|
||||||
|
|
||||||
// 关联
|
|
||||||
client OidcClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@map("oidc_authorization_codes")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统一令牌表(合并access和refresh token)
|
|
||||||
model OidcToken {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
token String @unique
|
|
||||||
userId String @map("user_id")
|
|
||||||
clientId String @map("client_id")
|
|
||||||
tokenType String @map("token_type") // "access" 或 "refresh"
|
|
||||||
scope String
|
|
||||||
expiresAt DateTime @map("expires_at")
|
|
||||||
createdTime DateTime @default(now()) @map("created_time")
|
|
||||||
isRevoked Boolean @default(false) @map("is_revoked")
|
|
||||||
parentId String? @map("parent_id") // 用于关联refresh token和对应的access token
|
|
||||||
|
|
||||||
// 关联
|
|
||||||
client OidcClient @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@index([userId, tokenType, isRevoked])
|
|
||||||
@@map("oidc_tokens")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session管理
|
|
||||||
model OidcSession {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
sessionId String @unique @map("session_id")
|
|
||||||
userId String @map("user_id")
|
|
||||||
expiresAt DateTime @map("expires_at")
|
|
||||||
lastActive DateTime @default(now()) @map("last_active")
|
|
||||||
deviceInfo String? @map("device_info")
|
|
||||||
createdTime DateTime @default(now()) @map("created_time")
|
|
||||||
lastModifiedTime DateTime? @updatedAt @map("last_modified_time")
|
|
||||||
|
|
||||||
@@map("oidc_sessions")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 供应商的密钥对
|
|
||||||
model OidcKeyPair {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
kid String @unique
|
|
||||||
privateKey String @map("private_key")
|
|
||||||
publicKey String @map("public_key")
|
|
||||||
algorithm String
|
|
||||||
active Boolean @default(true)
|
|
||||||
createdTime DateTime @default(now()) @map("created_time")
|
|
||||||
expiresAt DateTime? @map("expires_at")
|
|
||||||
|
|
||||||
@@map("oidc_key_pairs")
|
|
||||||
}
|
|
||||||
|
|
|
@ -53,6 +53,9 @@ importers:
|
||||||
ioredis:
|
ioredis:
|
||||||
specifier: 5.4.1
|
specifier: 5.4.1
|
||||||
version: 5.4.1
|
version: 5.4.1
|
||||||
|
jose:
|
||||||
|
specifier: ^6.0.11
|
||||||
|
version: 6.0.11
|
||||||
minio:
|
minio:
|
||||||
specifier: 7.1.3
|
specifier: 7.1.3
|
||||||
version: 7.1.3
|
version: 7.1.3
|
||||||
|
|
Loading…
Reference in New Issue