05262157
This commit is contained in:
parent
251260aace
commit
0e08e6ff2c
|
@ -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
|
|
@ -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,7 +1,9 @@
|
||||||
import { Configuration } from 'oidc-provider';
|
import { Configuration } from 'oidc-provider';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
import { RedisAdapter } from './redis-adapter';
|
||||||
|
|
||||||
const config: Configuration = {
|
const config: Configuration = {
|
||||||
|
adapter: RedisAdapter,
|
||||||
clients: [
|
clients: [
|
||||||
{
|
{
|
||||||
client_id: 'example-client',
|
client_id: 'example-client',
|
||||||
|
|
|
@ -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}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1 @@
|
||||||
|
|
||||||
export * from "./useEntity"
|
export * from "./useEntity"
|
||||||
|
|
|
@ -98,121 +98,3 @@ model UserLastVisit {
|
||||||
@@index([userId, resourceType])
|
@@index([userId, resourceType])
|
||||||
@@map("user_last_visit")
|
@@map("user_last_visit")
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDC 客户端相关模型
|
|
||||||
model OidcClient {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
clientId String @unique @map("client_id")
|
|
||||||
clientSecret String? @map("client_secret")
|
|
||||||
clientName String @map("client_name")
|
|
||||||
clientUri String? @map("client_uri")
|
|
||||||
logoUri String? @map("logo_uri")
|
|
||||||
contacts String[]
|
|
||||||
redirectUris String[] @map("redirect_uris")
|
|
||||||
postLogoutRedirectUris String[] @map("post_logout_redirect_uris")
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用户同意记录
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue