diff --git a/.gitignore b/.gitignore index af3063c..edd0df0 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ npm-debug.log* *.pem -packages/db/generated \ No newline at end of file +packages/db/generated +volumes \ No newline at end of file diff --git a/apps/backend/src/oidc/config.ts b/apps/backend/src/oidc/config.ts index 9d857b7..ec05f27 100644 --- a/apps/backend/src/oidc/config.ts +++ b/apps/backend/src/oidc/config.ts @@ -1,39 +1,105 @@ import { Configuration } from 'oidc-provider'; -import { nanoid } from 'nanoid'; import { RedisAdapter } from './redis-adapter'; +import { prisma } from '@repo/db'; +import { generateKeyPairSync } from 'crypto'; -const config: Configuration = { - adapter: RedisAdapter, - clients: [ +// 自动生成JWKS密钥对(如无环境变量则自动生成,建议持久化到安全存储) +function getJWKS() { + if (process.env.OIDC_JWKS) { + return JSON.parse(process.env.OIDC_JWKS); + } + // 生成RSA密钥对 + const { publicKey, privateKey } = generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + }); + // 转为JWK格式(这里只做简单转换,生产建议用jose等库) + const jwk = require('pem-jwk').pem2jwk(publicKey); + jwk.use = 'sig'; + jwk.alg = 'RS256'; + // 打印公钥,便于配置到前端或备份 + console.log('自动生成的OIDC公钥JWK:', JSON.stringify(jwk, null, 2)); + return [jwk]; +} + +// 支持从数据库动态加载clients +async function getClients() { + const dbClients = await prisma.oidcClient.findMany?.(); + if (dbClients && dbClients.length > 0) { + return 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, + })); + } + return [ { - client_id: 'example-client', - client_secret: 'example-secret', + client_id: process.env.OIDC_CLIENT_ID || 'example-client', + client_secret: process.env.OIDC_CLIENT_SECRET || 'example-secret', grant_types: ['authorization_code', 'refresh_token'], - redirect_uris: ['http://localhost:3000/callback'], + redirect_uris: [process.env.OIDC_REDIRECT_URI || 'http://localhost:3000/callback'], response_types: ['code'], scope: 'openid email profile', }, - ], + ]; +} + +const OIDC_COOKIE_KEY = process.env.OIDC_COOKIE_KEY || 'default-cookie-key'; + +const config: Configuration = { + adapter: RedisAdapter, + // 注意:clients字段现在是Promise,需在Provider初始化时await + clients: await getClients(), pkce: { - required: () => true, // 要求所有客户端使用PKCE + required: () => true, }, features: { - devInteractions: { enabled: false }, // 禁用开发交互界面 - resourceIndicators: { enabled: true }, // 启用资源指示器 - revocation: { enabled: true }, // 启用令牌撤销 - userinfo: { enabled: true }, // 启用用户信息端点 + devInteractions: { enabled: false }, + resourceIndicators: { enabled: true }, + revocation: { enabled: true }, + userinfo: { enabled: true }, + registration: { enabled: true }, }, cookies: { - keys: [nanoid()], // 用于签署和验证cookie + keys: [OIDC_COOKIE_KEY], }, jwks: { - keys: [], // 在实际环境中应该生成并保存密钥 + keys: getJWKS(), }, ttl: { - AccessToken: 3600, // 1小时 - AuthorizationCode: 600, // 10分钟 - IdToken: 3600, // 1小时 - RefreshToken: 1209600, // 14天 + AccessToken: 3600, + AuthorizationCode: 600, + IdToken: 3600, + 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, + }; + }, + }; }, }; diff --git a/apps/backend/src/redis.ts b/apps/backend/src/redis.ts index b2aebe3..494dc10 100644 --- a/apps/backend/src/redis.ts +++ b/apps/backend/src/redis.ts @@ -4,7 +4,7 @@ import Redis from 'ioredis'; const redis = new Redis({ host: 'localhost', // 根据实际情况配置 port: 6379, - // password: 'yourpassword', // 如有需要 + password: process.env.REDIS_PASSWORD }); export default redis; \ No newline at end of file diff --git a/packages/db/prisma/migrations/20250526141831_add_oidc_client/migration.sql b/packages/db/prisma/migrations/20250526141831_add_oidc_client/migration.sql new file mode 100644 index 0000000..ace7e67 --- /dev/null +++ b/packages/db/prisma/migrations/20250526141831_add_oidc_client/migration.sql @@ -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"); diff --git a/packages/db/prisma/migrations/migration_lock.toml b/packages/db/prisma/migrations/migration_lock.toml index fbffa92..044d57c 100644 --- a/packages/db/prisma/migrations/migration_lock.toml +++ b/packages/db/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 309f64b..6c5b6c2 100755 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -98,3 +98,17 @@ model UserLastVisit { @@index([userId, resourceType]) @@map("user_last_visit") } + +model OidcClient { + id String @id @default(cuid()) + clientId String @unique + clientSecret String + redirectUris String // 存储为JSON字符串 + grantTypes String // 存储为JSON字符串 + responseTypes String // 存储为JSON字符串 + scope String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("oidc_clients") +}