Merge branch 'main' of http://113.45.67.59:3003/insiinc/nice
This commit is contained in:
commit
862d3bef72
|
@ -10,7 +10,10 @@
|
|||
"@hono/zod-validator": "^0.5.0",
|
||||
"@repo/db": "workspace:*",
|
||||
"@repo/oidc-provider": "workspace:*",
|
||||
"@repo/tus": "workspace:*",
|
||||
"@repo/storage": "workspace:*",
|
||||
"@trpc/server": "11.1.2",
|
||||
"dayjs": "^1.11.12",
|
||||
"hono": "^4.7.10",
|
||||
"ioredis": "5.4.1",
|
||||
"jose": "^6.0.11",
|
||||
|
@ -19,9 +22,9 @@
|
|||
"node-cron": "^4.0.7",
|
||||
"oidc-provider": "^9.1.1",
|
||||
"superjson": "^2.2.2",
|
||||
"transliteration": "^2.3.5",
|
||||
"valibot": "^1.1.0",
|
||||
"zod": "^3.25.23",
|
||||
"@repo/storage": "workspace:*"
|
||||
"zod": "^3.25.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
# OIDC Provider
|
||||
|
||||
OpenID Connect Provider 实现,支持标准的 OIDC 协议流程。
|
||||
|
||||
## 特性
|
||||
|
||||
- 完整的 OIDC 协议支持
|
||||
- 密码认证策略
|
||||
- 会话管理
|
||||
- 令牌管理(访问令牌、刷新令牌、ID令牌)
|
||||
- PKCE 支持
|
||||
- 可自定义的存储适配器
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装
|
||||
|
||||
```bash
|
||||
npm install @nice/oidc-provider
|
||||
```
|
||||
|
||||
### 2. 配置
|
||||
|
||||
```typescript
|
||||
import { createOIDCProvider } from '@nice/oidc-provider/middleware/hono';
|
||||
import { MemoryStorageAdapter } from '@nice/oidc-provider/storage';
|
||||
|
||||
const config = {
|
||||
issuer: 'https://your-domain.com',
|
||||
signingKey: 'your-signing-key',
|
||||
storage: new MemoryStorageAdapter(),
|
||||
|
||||
// 用户和客户端查找函数
|
||||
findUser: async (userId: string) => {
|
||||
// 从数据库查找用户
|
||||
return await db.user.findUnique({ where: { id: userId } });
|
||||
},
|
||||
|
||||
findClient: async (clientId: string) => {
|
||||
// 从数据库查找客户端
|
||||
return await db.client.findUnique({ where: { id: clientId } });
|
||||
},
|
||||
|
||||
// 认证配置
|
||||
authConfig: {
|
||||
// 密码验证器
|
||||
passwordValidator: async (username: string, password: string) => {
|
||||
const user = await db.user.findUnique({ where: { username } });
|
||||
if (user && await bcrypt.compare(password, user.hashedPassword)) {
|
||||
return user.id;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// 会话配置
|
||||
sessionTTL: 24 * 60 * 60, // 24小时
|
||||
rememberMeMaxAge: 30 * 24 * 60 * 60, // 30天
|
||||
|
||||
// 页面配置
|
||||
pageConfig: {
|
||||
title: '用户登录',
|
||||
brandName: '我的应用',
|
||||
logoUrl: '/logo.png'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 创建 OIDC Provider Hono 应用
|
||||
const oidcApp = createOIDCProvider(config);
|
||||
```
|
||||
|
||||
### 3. 集成到 Hono 应用
|
||||
|
||||
```typescript
|
||||
import { Hono } from 'hono';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
// 挂载 OIDC Provider
|
||||
app.route('/oidc', oidcApp);
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
## API 端点
|
||||
|
||||
创建后的 OIDC Provider 将提供以下标准端点:
|
||||
|
||||
- `POST /login` - 用户登录
|
||||
- `GET /logout` - 用户登出
|
||||
- `POST /logout` - 用户登出(POST 方式)
|
||||
- `GET /.well-known/openid-configuration` - OIDC 发现文档
|
||||
- `GET /.well-known/jwks.json` - JSON Web Key Set
|
||||
- `GET /auth` - 授权端点
|
||||
- `POST /token` - 令牌端点
|
||||
- `GET /userinfo` - 用户信息端点
|
||||
- `POST /revoke` - 令牌撤销端点
|
||||
- `POST /introspect` - 令牌内省端点
|
||||
|
||||
## 配置选项
|
||||
|
||||
### OIDCProviderConfig
|
||||
|
||||
| 字段 | 类型 | 必需 | 描述 |
|
||||
|------|------|------|------|
|
||||
| `issuer` | string | ✓ | 发行者标识符 |
|
||||
| `signingKey` | string | ✓ | JWT 签名密钥 |
|
||||
| `storage` | StorageAdapter | ✓ | 存储适配器 |
|
||||
| `findUser` | function | ✓ | 用户查找函数 |
|
||||
| `findClient` | function | ✓ | 客户端查找函数 |
|
||||
| `authConfig` | AuthConfig | - | 认证配置 |
|
||||
| `tokenTTL` | TokenTTLConfig | - | 令牌过期时间配置 |
|
||||
|
||||
### AuthConfig
|
||||
|
||||
| 字段 | 类型 | 必需 | 描述 |
|
||||
|------|------|------|------|
|
||||
| `passwordValidator` | function | - | 密码验证函数 |
|
||||
| `sessionTTL` | number | - | 会话过期时间(秒) |
|
||||
| `rememberMeMaxAge` | number | - | 记住我最长时间(秒) |
|
||||
| `pageConfig` | PageConfig | - | 登录页面配置 |
|
||||
|
||||
### PageConfig
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `title` | string | 登录页面标题 |
|
||||
| `brandName` | string | 品牌名称 |
|
||||
| `logoUrl` | string | Logo URL |
|
||||
|
||||
## 存储适配器
|
||||
|
||||
项目提供了多种存储适配器:
|
||||
|
||||
- `MemoryStorageAdapter` - 内存存储(适用于开发和测试)
|
||||
- `RedisStorageAdapter` - Redis 存储
|
||||
- `DatabaseStorageAdapter` - 数据库存储
|
||||
|
||||
### 自定义存储适配器
|
||||
|
||||
```typescript
|
||||
import { StorageAdapter } from '@nice/oidc-provider/storage';
|
||||
|
||||
class CustomStorageAdapter implements StorageAdapter {
|
||||
// 实现所需的方法
|
||||
}
|
||||
```
|
||||
|
||||
## 安全考虑
|
||||
|
||||
1. **签名密钥安全**:确保 `signingKey` 足够复杂且妥善保管
|
||||
2. **HTTPS**:生产环境必须使用 HTTPS
|
||||
3. **客户端验证**:实现严格的客户端验证逻辑
|
||||
4. **密码策略**:在 `passwordValidator` 中实现适当的密码策略
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
|
@ -1,157 +0,0 @@
|
|||
import type { Context } from 'hono';
|
||||
import type { AuthorizationRequest, OIDCProviderConfig } from '../types';
|
||||
import { PasswordAuthStrategy, type AuthenticationResult, type PasswordAuthConfig } from './strategies/password-auth-strategy';
|
||||
|
||||
/**
|
||||
* 密码验证器类型
|
||||
*/
|
||||
export type PasswordValidator = (username: string, password: string) => Promise<string | null>;
|
||||
|
||||
/**
|
||||
* 认证管理器配置
|
||||
*/
|
||||
export interface AuthManagerConfig {
|
||||
/** 会话TTL(秒) */
|
||||
sessionTTL?: number;
|
||||
/** 记住我最大存活时间(秒) */
|
||||
rememberMeMaxAge?: number;
|
||||
/** 页面配置 */
|
||||
pageConfig?: {
|
||||
title?: string;
|
||||
brandName?: string;
|
||||
logoUrl?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证管理器
|
||||
* 专门处理密码认证
|
||||
*/
|
||||
export class AuthManager {
|
||||
private readonly passwordAuth: PasswordAuthStrategy;
|
||||
|
||||
constructor(
|
||||
oidcConfig: OIDCProviderConfig,
|
||||
passwordValidator: PasswordValidator,
|
||||
config: AuthManagerConfig = {}
|
||||
) {
|
||||
const passwordConfig: PasswordAuthConfig = {
|
||||
sessionTTL: config.sessionTTL || 24 * 60 * 60, // 默认24小时
|
||||
rememberMeMaxAge: config.rememberMeMaxAge || 30 * 24 * 60 * 60, // 默认30天
|
||||
pageConfig: config.pageConfig || {}
|
||||
};
|
||||
|
||||
this.passwordAuth = new PasswordAuthStrategy(
|
||||
oidcConfig,
|
||||
passwordValidator,
|
||||
passwordConfig
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否已认证
|
||||
*/
|
||||
async getCurrentUser(c: Context): Promise<string | null> {
|
||||
return await this.passwordAuth.getCurrentUser(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理认证要求
|
||||
*/
|
||||
async handleAuthenticationRequired(
|
||||
c: Context,
|
||||
authRequest: AuthorizationRequest
|
||||
): Promise<Response> {
|
||||
return await this.passwordAuth.handleAuthenticationRequired(c, authRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理认证请求
|
||||
*/
|
||||
async authenticate(c: Context): Promise<AuthenticationResult> {
|
||||
return await this.passwordAuth.authenticate(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登出请求
|
||||
*/
|
||||
async logout(c: Context): Promise<Response> {
|
||||
return await this.passwordAuth.logout(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理完整的登录流程
|
||||
*/
|
||||
async handleLogin(c: Context, authRequest: AuthorizationRequest): Promise<Response> {
|
||||
try {
|
||||
// 执行认证
|
||||
const authResult = await this.passwordAuth.authenticate(c);
|
||||
|
||||
if (authResult.success) {
|
||||
// 认证成功,处理后续操作
|
||||
return await this.handleAuthenticationSuccess(c, authResult);
|
||||
} else {
|
||||
// 认证失败,返回错误页面
|
||||
return await this.handleAuthenticationFailure(c, authResult, authRequest);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录流程处理失败:', error);
|
||||
|
||||
// 返回通用错误页面
|
||||
return await this.handleAuthenticationFailure(
|
||||
c,
|
||||
{ success: false, error: '服务器内部错误' },
|
||||
authRequest
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查认证状态并处理
|
||||
*/
|
||||
async checkAuthenticationStatus(
|
||||
c: Context,
|
||||
authRequest: AuthorizationRequest
|
||||
): Promise<{ authenticated: boolean; userId?: string; response?: Response }> {
|
||||
try {
|
||||
const userId = await this.getCurrentUser(c);
|
||||
|
||||
if (userId) {
|
||||
return {
|
||||
authenticated: true,
|
||||
userId
|
||||
};
|
||||
} else {
|
||||
const response = await this.handleAuthenticationRequired(c, authRequest);
|
||||
return {
|
||||
authenticated: false,
|
||||
response
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查认证状态失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理认证成功
|
||||
*/
|
||||
private async handleAuthenticationSuccess(
|
||||
c: Context,
|
||||
result: AuthenticationResult
|
||||
): Promise<Response> {
|
||||
return await this.passwordAuth.handleAuthenticationSuccess(c, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理认证失败
|
||||
*/
|
||||
private async handleAuthenticationFailure(
|
||||
c: Context,
|
||||
result: AuthenticationResult,
|
||||
authRequest: AuthorizationRequest
|
||||
): Promise<Response> {
|
||||
return await this.passwordAuth.handleAuthenticationFailure(c, result, authRequest);
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
// 核心管理器
|
||||
export { AuthManager, type AuthManagerConfig, type PasswordValidator } from './auth-manager';
|
||||
|
||||
// 密码认证
|
||||
export { PasswordAuthStrategy, type AuthenticationResult, type PasswordAuthConfig } from './strategies/password-auth-strategy';
|
||||
export { PasswordAuth, type AuthenticationResult, type PasswordAuthConfig } from './password-auth';
|
||||
|
||||
// 类型定义(从 types 模块重新导出)
|
||||
export type { PasswordValidator } from '../types';
|
||||
|
||||
// 工具类
|
||||
export { CookieUtils, type CookieConfig } from './utils/cookie-utils';
|
||||
export { HtmlTemplates, type PageConfig } from './utils/html-templates';
|
||||
|
||||
// 原有组件(向后兼容)
|
||||
// 存储管理器
|
||||
export { TokenManager } from './token-manager';
|
||||
export { SessionManager } from './session-manager';
|
|
@ -1,8 +1,8 @@
|
|||
import type { Context } from 'hono';
|
||||
import type { AuthorizationRequest, LoginCredentials, PasswordValidator, OIDCProviderConfig } from '../../types';
|
||||
import { SessionManager } from '../session-manager';
|
||||
import { CookieUtils } from '../utils/cookie-utils';
|
||||
import { HtmlTemplates, type PageConfig } from '../utils/html-templates';
|
||||
import { SessionManager } from './session-manager';
|
||||
import { CookieUtils } from './utils/cookie-utils';
|
||||
import { HtmlTemplates, type PageConfig } from './utils/html-templates';
|
||||
import { OIDCProviderConfig, PasswordValidator, AuthorizationRequest, LoginCredentials } from '../types';
|
||||
|
||||
/**
|
||||
* 认证结果接口
|
||||
|
@ -31,14 +31,18 @@ export interface PasswordAuthConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* 密码认证服务
|
||||
* 密码认证策略
|
||||
* 实现基于用户名和密码的传统认证方式
|
||||
*/
|
||||
export class PasswordAuthStrategy {
|
||||
export class PasswordAuth {
|
||||
readonly name = 'password';
|
||||
|
||||
private readonly sessionManager: SessionManager;
|
||||
private readonly config: PasswordAuthConfig;
|
||||
private readonly config: {
|
||||
sessionTTL: number;
|
||||
rememberMeMaxAge: number;
|
||||
pageConfig?: PageConfig;
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly oidcConfig: OIDCProviderConfig,
|
||||
|
@ -46,8 +50,8 @@ export class PasswordAuthStrategy {
|
|||
config: PasswordAuthConfig = {}
|
||||
) {
|
||||
this.config = {
|
||||
sessionTTL: 24 * 60 * 60, // 默认24小时
|
||||
rememberMeMaxAge: 30 * 24 * 60 * 60, // 默认30天
|
||||
sessionTTL: 24 * 60 * 60, // 24小时
|
||||
rememberMeMaxAge: 30 * 24 * 60 * 60, // 30天
|
||||
...config
|
||||
};
|
||||
this.sessionManager = new SessionManager(oidcConfig.storage, this.config.sessionTTL);
|
||||
|
@ -58,9 +62,7 @@ export class PasswordAuthStrategy {
|
|||
*/
|
||||
async getCurrentUser(c: Context): Promise<string | null> {
|
||||
const sessionId = CookieUtils.getSessionIdFromCookie(c);
|
||||
if (!sessionId) {
|
||||
return null;
|
||||
}
|
||||
if (!sessionId) return null;
|
||||
|
||||
try {
|
||||
const session = await this.sessionManager.getSession(sessionId);
|
||||
|
@ -76,7 +78,6 @@ export class PasswordAuthStrategy {
|
|||
*/
|
||||
async handleAuthenticationRequired(c: Context, authRequest: AuthorizationRequest): Promise<Response> {
|
||||
const loginHtml = HtmlTemplates.generateLoginPage(authRequest, this.config.pageConfig);
|
||||
|
||||
c.header('Content-Type', 'text/html; charset=utf-8');
|
||||
return c.body(loginHtml);
|
||||
}
|
||||
|
@ -86,7 +87,6 @@ export class PasswordAuthStrategy {
|
|||
*/
|
||||
async authenticate(c: Context): Promise<AuthenticationResult> {
|
||||
try {
|
||||
// 解析表单数据
|
||||
const formData = await c.req.formData();
|
||||
const credentials = this.parseCredentials(formData);
|
||||
const authParams = this.extractAuthParams(formData);
|
||||
|
@ -94,41 +94,27 @@ export class PasswordAuthStrategy {
|
|||
// 验证输入
|
||||
const validationError = this.validateCredentials(credentials);
|
||||
if (validationError) {
|
||||
return {
|
||||
success: false,
|
||||
error: validationError
|
||||
};
|
||||
return { success: false, error: validationError };
|
||||
}
|
||||
|
||||
// 验证用户密码
|
||||
const userId = await this.passwordValidator(credentials.username, credentials.password);
|
||||
if (!userId) {
|
||||
return {
|
||||
success: false,
|
||||
error: '用户名或密码错误'
|
||||
};
|
||||
return { success: false, error: '用户名或密码错误' };
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
const user = await this.oidcConfig.findUser(userId);
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
error: '用户不存在'
|
||||
};
|
||||
return { success: false, error: '用户不存在' };
|
||||
}
|
||||
|
||||
// 创建会话
|
||||
const session = await this.sessionManager.createSession(
|
||||
userId,
|
||||
authParams.client_id,
|
||||
authParams
|
||||
);
|
||||
const session = await this.sessionManager.createSession(userId, authParams.client_id, authParams);
|
||||
|
||||
// 设置Cookie
|
||||
const maxAge = credentials.remember_me
|
||||
? this.config.rememberMeMaxAge!
|
||||
: this.config.sessionTTL!;
|
||||
? this.config.rememberMeMaxAge
|
||||
: this.config.sessionTTL;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
@ -140,13 +126,9 @@ export class PasswordAuthStrategy {
|
|||
rememberMe: credentials.remember_me
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('认证处理失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: '服务器内部错误,请稍后重试'
|
||||
};
|
||||
return { success: false, error: '服务器内部错误,请稍后重试' };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,10 +146,8 @@ export class PasswordAuthStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
// 清除Cookie
|
||||
CookieUtils.clearSessionCookie(c);
|
||||
|
||||
// 处理重定向
|
||||
const postLogoutRedirectUri = c.req.query('post_logout_redirect_uri');
|
||||
const state = c.req.query('state');
|
||||
|
||||
|
@ -185,20 +165,14 @@ export class PasswordAuthStrategy {
|
|||
/**
|
||||
* 处理认证成功后的操作
|
||||
*/
|
||||
async handleAuthenticationSuccess(
|
||||
c: Context,
|
||||
result: AuthenticationResult
|
||||
): Promise<Response> {
|
||||
async handleAuthenticationSuccess(c: Context, result: AuthenticationResult): Promise<Response> {
|
||||
if (!result.success || !result.metadata) {
|
||||
throw new Error('认证结果无效');
|
||||
}
|
||||
|
||||
const { sessionId, maxAge, authParams } = result.metadata;
|
||||
|
||||
// 设置会话Cookie
|
||||
CookieUtils.setSessionCookie(c, sessionId, maxAge);
|
||||
|
||||
// 重定向到授权端点
|
||||
const authUrl = this.buildAuthorizationUrl(authParams);
|
||||
return c.redirect(authUrl);
|
||||
}
|
||||
|
@ -211,12 +185,7 @@ export class PasswordAuthStrategy {
|
|||
result: AuthenticationResult,
|
||||
authRequest: AuthorizationRequest
|
||||
): Promise<Response> {
|
||||
const errorHtml = HtmlTemplates.generateLoginPage(
|
||||
authRequest,
|
||||
this.config.pageConfig,
|
||||
result.error
|
||||
);
|
||||
|
||||
const errorHtml = HtmlTemplates.generateLoginPage(authRequest, this.config.pageConfig, result.error);
|
||||
c.header('Content-Type', 'text/html; charset=utf-8');
|
||||
return c.body(errorHtml);
|
||||
}
|
||||
|
@ -236,22 +205,10 @@ export class PasswordAuthStrategy {
|
|||
* 验证登录凭据
|
||||
*/
|
||||
private validateCredentials(credentials: LoginCredentials): string | null {
|
||||
if (!credentials.username.trim()) {
|
||||
return '请输入用户名';
|
||||
}
|
||||
|
||||
if (!credentials.password) {
|
||||
return '请输入密码';
|
||||
}
|
||||
|
||||
if (credentials.username.length > 100) {
|
||||
return '用户名过长';
|
||||
}
|
||||
|
||||
if (credentials.password.length > 200) {
|
||||
return '密码过长';
|
||||
}
|
||||
|
||||
if (!credentials.username.trim()) return '请输入用户名';
|
||||
if (!credentials.password) return '请输入密码';
|
||||
if (credentials.username.length > 100) return '用户名过长';
|
||||
if (credentials.password.length > 200) return '密码过长';
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -259,9 +216,7 @@ export class PasswordAuthStrategy {
|
|||
* 构建登出后重定向URL
|
||||
*/
|
||||
private buildPostLogoutRedirectUrl(redirectUri: string, state?: string): string {
|
||||
if (!state) {
|
||||
return redirectUri;
|
||||
}
|
||||
if (!state) return redirectUri;
|
||||
|
||||
const url = new URL(redirectUri);
|
||||
url.searchParams.set('state', state);
|
||||
|
@ -271,7 +226,7 @@ export class PasswordAuthStrategy {
|
|||
/**
|
||||
* 构建授权URL
|
||||
*/
|
||||
protected buildAuthorizationUrl(authParams: AuthorizationRequest): string {
|
||||
private buildAuthorizationUrl(authParams: AuthorizationRequest): string {
|
||||
const params = new URLSearchParams();
|
||||
params.set('response_type', authParams.response_type);
|
||||
params.set('client_id', authParams.client_id);
|
||||
|
@ -289,7 +244,7 @@ export class PasswordAuthStrategy {
|
|||
/**
|
||||
* 从表单数据提取授权参数
|
||||
*/
|
||||
protected extractAuthParams(formData: FormData): AuthorizationRequest {
|
||||
private extractAuthParams(formData: FormData): AuthorizationRequest {
|
||||
return {
|
||||
response_type: formData.get('response_type')?.toString() || '',
|
||||
client_id: formData.get('client_id')?.toString() || '',
|
|
@ -1,5 +1,5 @@
|
|||
import type { StorageAdapter } from '../storage';
|
||||
import type { AuthorizationRequest } from '../types';
|
||||
import type { StorageAdapter } from '../storage';
|
||||
|
||||
/**
|
||||
* 会话数据接口
|
||||
|
@ -19,7 +19,7 @@ interface SessionData {
|
|||
export class SessionManager {
|
||||
constructor(private storage: StorageAdapter, private sessionTTL: number = 3600) { }
|
||||
|
||||
private getSessionKey(sessionId: string): string {
|
||||
private getKey(sessionId: string): string {
|
||||
return `session:${sessionId}`;
|
||||
}
|
||||
|
||||
|
@ -59,17 +59,17 @@ export class SessionManager {
|
|||
}
|
||||
|
||||
async storeSession(sessionId: string, data: any): Promise<void> {
|
||||
const key = this.getSessionKey(sessionId);
|
||||
const key = this.getKey(sessionId);
|
||||
await this.storage.set(key, data, this.sessionTTL);
|
||||
}
|
||||
|
||||
async getSession(sessionId: string): Promise<any> {
|
||||
const key = this.getSessionKey(sessionId);
|
||||
const key = this.getKey(sessionId);
|
||||
return await this.storage.get(key);
|
||||
}
|
||||
|
||||
async deleteSession(sessionId: string): Promise<void> {
|
||||
const key = this.getSessionKey(sessionId);
|
||||
const key = this.getKey(sessionId);
|
||||
await this.storage.delete(key);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,14 @@ import type { StorageAdapter } from '../storage';
|
|||
export class TokenManager {
|
||||
constructor(private storage: StorageAdapter) { }
|
||||
|
||||
// 生成存储键
|
||||
/**
|
||||
* 计算TTL(秒)
|
||||
*/
|
||||
private calculateTTL(expiresAt: Date): number {
|
||||
return Math.max(Math.floor((expiresAt.getTime() - Date.now()) / 1000), 1);
|
||||
}
|
||||
|
||||
// 生成令牌存储键
|
||||
private getTokenKey(type: string, token: string): string {
|
||||
return `${type}:${token}`;
|
||||
}
|
||||
|
@ -25,27 +32,13 @@ export class TokenManager {
|
|||
// 授权码管理
|
||||
async storeAuthorizationCode(authCode: AuthorizationCode): Promise<void> {
|
||||
const key = this.getTokenKey('auth_code', authCode.code);
|
||||
const ttl = Math.floor((authCode.expires_at.getTime() - Date.now()) / 1000);
|
||||
|
||||
const data = {
|
||||
...authCode,
|
||||
expires_at: authCode.expires_at.toISOString(),
|
||||
created_at: authCode.created_at.toISOString(),
|
||||
};
|
||||
|
||||
await this.storage.set(key, data, Math.max(ttl, 1));
|
||||
const ttl = this.calculateTTL(authCode.expires_at);
|
||||
await this.storage.set(key, authCode, ttl);
|
||||
}
|
||||
|
||||
async getAuthorizationCode(code: string): Promise<AuthorizationCode | null> {
|
||||
const key = this.getTokenKey('auth_code', code);
|
||||
const data = await this.storage.get(key);
|
||||
if (!data) return null;
|
||||
|
||||
return {
|
||||
...data,
|
||||
expires_at: new Date(data.expires_at),
|
||||
created_at: new Date(data.created_at),
|
||||
};
|
||||
return await this.storage.get<AuthorizationCode>(key);
|
||||
}
|
||||
|
||||
async deleteAuthorizationCode(code: string): Promise<void> {
|
||||
|
@ -57,33 +50,20 @@ export class TokenManager {
|
|||
async storeAccessToken(token: AccessToken): Promise<void> {
|
||||
const key = this.getTokenKey('access_token', token.token);
|
||||
const userClientKey = this.getUserClientKey('access_tokens', token.user_id, token.client_id);
|
||||
const ttl = Math.floor((token.expires_at.getTime() - Date.now()) / 1000);
|
||||
|
||||
const data = {
|
||||
...token,
|
||||
expires_at: token.expires_at.toISOString(),
|
||||
created_at: token.created_at.toISOString(),
|
||||
};
|
||||
const ttl = this.calculateTTL(token.expires_at);
|
||||
|
||||
// 存储令牌数据
|
||||
await this.storage.set(key, data, Math.max(ttl, 1));
|
||||
await this.storage.set(key, token, ttl);
|
||||
|
||||
// 存储用户-客户端索引
|
||||
const existingTokens = await this.storage.get<string[]>(userClientKey) || [];
|
||||
existingTokens.push(token.token);
|
||||
await this.storage.set(userClientKey, existingTokens, Math.max(ttl, 1));
|
||||
await this.storage.set(userClientKey, existingTokens, ttl);
|
||||
}
|
||||
|
||||
async getAccessToken(token: string): Promise<AccessToken | null> {
|
||||
const key = this.getTokenKey('access_token', token);
|
||||
const data = await this.storage.get(key);
|
||||
if (!data) return null;
|
||||
|
||||
return {
|
||||
...data,
|
||||
expires_at: new Date(data.expires_at),
|
||||
created_at: new Date(data.created_at),
|
||||
};
|
||||
return await this.storage.get<AccessToken>(key);
|
||||
}
|
||||
|
||||
async deleteAccessToken(token: string): Promise<void> {
|
||||
|
@ -123,33 +103,20 @@ export class TokenManager {
|
|||
async storeRefreshToken(token: RefreshToken): Promise<void> {
|
||||
const key = this.getTokenKey('refresh_token', token.token);
|
||||
const userClientKey = this.getUserClientKey('refresh_tokens', token.user_id, token.client_id);
|
||||
const ttl = Math.floor((token.expires_at.getTime() - Date.now()) / 1000);
|
||||
|
||||
const data = {
|
||||
...token,
|
||||
expires_at: token.expires_at.toISOString(),
|
||||
created_at: token.created_at.toISOString(),
|
||||
};
|
||||
const ttl = this.calculateTTL(token.expires_at);
|
||||
|
||||
// 存储令牌数据
|
||||
await this.storage.set(key, data, Math.max(ttl, 1));
|
||||
await this.storage.set(key, token, ttl);
|
||||
|
||||
// 存储用户-客户端索引
|
||||
const existingTokens = await this.storage.get<string[]>(userClientKey) || [];
|
||||
existingTokens.push(token.token);
|
||||
await this.storage.set(userClientKey, existingTokens, Math.max(ttl, 1));
|
||||
await this.storage.set(userClientKey, existingTokens, ttl);
|
||||
}
|
||||
|
||||
async getRefreshToken(token: string): Promise<RefreshToken | null> {
|
||||
const key = this.getTokenKey('refresh_token', token);
|
||||
const data = await this.storage.get(key);
|
||||
if (!data) return null;
|
||||
|
||||
return {
|
||||
...data,
|
||||
expires_at: new Date(data.expires_at),
|
||||
created_at: new Date(data.created_at),
|
||||
};
|
||||
return await this.storage.get<RefreshToken>(key);
|
||||
}
|
||||
|
||||
async deleteRefreshToken(token: string): Promise<void> {
|
||||
|
@ -175,27 +142,13 @@ export class TokenManager {
|
|||
// ID令牌管理
|
||||
async storeIDToken(token: IDToken): Promise<void> {
|
||||
const key = this.getTokenKey('id_token', token.token);
|
||||
const ttl = Math.floor((token.expires_at.getTime() - Date.now()) / 1000);
|
||||
|
||||
const data = {
|
||||
...token,
|
||||
expires_at: token.expires_at.toISOString(),
|
||||
created_at: token.created_at.toISOString(),
|
||||
};
|
||||
|
||||
await this.storage.set(key, data, Math.max(ttl, 1));
|
||||
const ttl = this.calculateTTL(token.expires_at);
|
||||
await this.storage.set(key, token, ttl);
|
||||
}
|
||||
|
||||
async getIDToken(token: string): Promise<IDToken | null> {
|
||||
const key = this.getTokenKey('id_token', token);
|
||||
const data = await this.storage.get(key);
|
||||
if (!data) return null;
|
||||
|
||||
return {
|
||||
...data,
|
||||
expires_at: new Date(data.expires_at),
|
||||
created_at: new Date(data.created_at),
|
||||
};
|
||||
return await this.storage.get<IDToken>(key);
|
||||
}
|
||||
|
||||
async deleteIDToken(token: string): Promise<void> {
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
import type { OIDCError } from '../types';
|
||||
|
||||
/**
|
||||
* OIDC错误处理工厂类
|
||||
* 用于创建标准化的OIDC错误响应
|
||||
*/
|
||||
export class OIDCErrorFactory {
|
||||
/**
|
||||
* 创建授权错误(带可选的重定向URI和state)
|
||||
*/
|
||||
static createAuthError(error: string, description: string, state?: string) {
|
||||
return {
|
||||
success: false as const,
|
||||
error: { error, error_description: description, state },
|
||||
redirectUri: undefined as string | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建令牌错误
|
||||
*/
|
||||
static createTokenError(error: string, description: string) {
|
||||
return {
|
||||
success: false as const,
|
||||
error: { error, error_description: description },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建简单错误(用于一般API响应)
|
||||
*/
|
||||
static createSimpleError(error: string, description: string): OIDCError {
|
||||
return { error, error_description: description };
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器错误
|
||||
*/
|
||||
static serverError(state?: string) {
|
||||
return this.createAuthError('server_error', 'Internal server error', state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无效令牌错误
|
||||
*/
|
||||
static invalidToken(description = 'Invalid token') {
|
||||
return this.createSimpleError('invalid_token', description);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无效请求错误
|
||||
*/
|
||||
static invalidRequest(description: string) {
|
||||
return this.createSimpleError('invalid_request', description);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无效客户端错误
|
||||
*/
|
||||
static invalidClient(description: string) {
|
||||
return this.createTokenError('invalid_client', description);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无效授权错误
|
||||
*/
|
||||
static invalidGrant(description: string) {
|
||||
return this.createTokenError('invalid_grant', description);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不支持的授权类型错误
|
||||
*/
|
||||
static unsupportedGrantType(description = 'Grant type not supported') {
|
||||
return this.createTokenError('unsupported_grant_type', description);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无效作用域错误
|
||||
*/
|
||||
static invalidScope(description: string) {
|
||||
return this.createTokenError('invalid_scope', description);
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要登录错误
|
||||
*/
|
||||
static loginRequired(description = 'User authentication is required', state?: string) {
|
||||
return this.createAuthError('login_required', description, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* PKCE相关错误
|
||||
*/
|
||||
static pkceError(description: string, state?: string) {
|
||||
return this.createAuthError('invalid_request', description, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建错误响应的URL参数
|
||||
*/
|
||||
static buildErrorResponse(error: OIDCError): URLSearchParams {
|
||||
const params = new URLSearchParams();
|
||||
Object.entries(error).forEach(([key, value]) => {
|
||||
if (value != null) params.set(key, String(value));
|
||||
});
|
||||
return params;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { OIDCErrorFactory } from './error-factory';
|
|
@ -1,7 +1,7 @@
|
|||
// 核心类
|
||||
// 重新导出核心组件
|
||||
export { OIDCProvider } from './provider';
|
||||
|
||||
// 类型定义
|
||||
// 重新导出类型定义
|
||||
export type {
|
||||
OIDCProviderConfig,
|
||||
OIDCClient,
|
||||
|
@ -20,41 +20,29 @@ export type {
|
|||
PasswordValidator,
|
||||
} from './types';
|
||||
|
||||
// 存储适配器接口和示例实现
|
||||
export type { StorageAdapter } from './storage';
|
||||
export { RedisStorageAdapter } from './storage';
|
||||
// 重新导出存储适配器
|
||||
export type { StorageAdapter } from './storage/adapter';
|
||||
export { RedisStorageAdapter } from './storage/redis-adapter';
|
||||
|
||||
// Hono中间件
|
||||
export {
|
||||
createOIDCProvider,
|
||||
oidcProvider,
|
||||
getOIDCProvider
|
||||
} from './middleware/hono';
|
||||
|
||||
// 导出中间件配置类型
|
||||
export type {
|
||||
OIDCHonoOptions
|
||||
} from './middleware/hono';
|
||||
|
||||
// 工具类
|
||||
// 重新导出JWT工具
|
||||
export { JWTUtils } from './utils/jwt';
|
||||
export { PKCEUtils } from './utils/pkce';
|
||||
export { ValidationUtils } from './utils/validation';
|
||||
|
||||
// 认证模块
|
||||
// 重新导出验证工具
|
||||
export { ValidationUtils } from './utils/validation';
|
||||
export { PKCEUtils } from './utils/pkce';
|
||||
|
||||
// 重新导出认证相关
|
||||
export {
|
||||
AuthManager,
|
||||
PasswordAuthStrategy,
|
||||
PasswordAuth,
|
||||
type AuthenticationResult,
|
||||
type PasswordAuthConfig,
|
||||
TokenManager,
|
||||
SessionManager,
|
||||
CookieUtils,
|
||||
HtmlTemplates,
|
||||
SessionManager,
|
||||
TokenManager
|
||||
type PageConfig,
|
||||
type CookieConfig,
|
||||
} from './auth';
|
||||
|
||||
export type {
|
||||
AuthManagerConfig,
|
||||
PasswordAuthConfig,
|
||||
AuthenticationResult,
|
||||
CookieConfig,
|
||||
PageConfig
|
||||
} from './auth';
|
||||
// 重新导出中间件
|
||||
export { createOIDCProvider, oidcProvider, getOIDCProvider } from './middleware/hono';
|
|
@ -1,313 +1,62 @@
|
|||
import { Hono } from 'hono';
|
||||
import type { Context, Next } from 'hono';
|
||||
import { OIDCProvider } from '../provider';
|
||||
import type { OIDCProviderConfig, AuthorizationRequest, TokenRequest } from '../types';
|
||||
import { AuthManager, type PasswordValidator } from '../auth';
|
||||
|
||||
/**
|
||||
* OIDC Provider配置选项
|
||||
*/
|
||||
export interface OIDCHonoOptions {
|
||||
/** OIDC Provider配置 */
|
||||
config: OIDCProviderConfig;
|
||||
/** 密码验证器 - 用于验证用户名和密码 */
|
||||
passwordValidator: PasswordValidator;
|
||||
/** 认证配置选项 */
|
||||
authConfig?: {
|
||||
/** 会话TTL(秒) */
|
||||
sessionTTL?: number;
|
||||
/** 登录页面标题 */
|
||||
loginPageTitle?: string;
|
||||
/** 品牌名称 */
|
||||
brandName?: string;
|
||||
/** 品牌Logo URL */
|
||||
logoUrl?: string;
|
||||
};
|
||||
}
|
||||
import type { OIDCProviderConfig } from '../types';
|
||||
|
||||
/**
|
||||
* 创建OIDC Provider Hono应用
|
||||
*/
|
||||
export function createOIDCProvider(options: OIDCHonoOptions): Hono {
|
||||
const { config, passwordValidator, authConfig = {} } = options;
|
||||
|
||||
// 创建认证管理器
|
||||
const authManager = new AuthManager(
|
||||
config,
|
||||
passwordValidator,
|
||||
{
|
||||
sessionTTL: authConfig.sessionTTL,
|
||||
pageConfig: {
|
||||
title: authConfig.loginPageTitle,
|
||||
brandName: authConfig.brandName,
|
||||
logoUrl: authConfig.logoUrl
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export function createOIDCProvider(config: OIDCProviderConfig): Hono {
|
||||
const app = new Hono();
|
||||
const provider = new OIDCProvider(config);
|
||||
|
||||
// 登录端点
|
||||
app.post('/login', async (c: Context) => {
|
||||
// 从表单中提取授权参数
|
||||
const formData = await c.req.formData();
|
||||
const authRequest = {
|
||||
response_type: formData.get('response_type')?.toString() || '',
|
||||
client_id: formData.get('client_id')?.toString() || '',
|
||||
redirect_uri: formData.get('redirect_uri')?.toString() || '',
|
||||
scope: formData.get('scope')?.toString() || '',
|
||||
state: formData.get('state')?.toString(),
|
||||
nonce: formData.get('nonce')?.toString(),
|
||||
code_challenge: formData.get('code_challenge')?.toString(),
|
||||
code_challenge_method: formData.get('code_challenge_method')?.toString() as 'plain' | 'S256' | undefined,
|
||||
};
|
||||
|
||||
return await authManager.handleLogin(c, authRequest);
|
||||
return await provider.handleLogin(c);
|
||||
});
|
||||
|
||||
// 登出端点
|
||||
app.get('/logout', async (c: Context) => {
|
||||
return await authManager.logout(c);
|
||||
return await provider.handleLogout(c);
|
||||
});
|
||||
|
||||
app.post('/logout', async (c: Context) => {
|
||||
return await authManager.logout(c);
|
||||
return await provider.handleLogout(c);
|
||||
});
|
||||
|
||||
// 发现文档端点
|
||||
app.get('/.well-known/openid-configuration', async (c: Context) => {
|
||||
const discovery = provider.getDiscoveryDocument();
|
||||
return c.json(discovery);
|
||||
// 发现文档端点 - 直接调用核心方法
|
||||
app.get('/.well-known/openid-configuration', (c: Context) => {
|
||||
return c.json(provider.getDiscoveryDocument());
|
||||
});
|
||||
|
||||
// JWKS端点
|
||||
// JWKS端点 - 直接调用核心方法
|
||||
app.get('/.well-known/jwks.json', async (c: Context) => {
|
||||
const jwks = await provider.getJWKS();
|
||||
return c.json(jwks);
|
||||
return c.json(await provider.getJWKS());
|
||||
});
|
||||
|
||||
// 授权端点 - 使用认证管理器
|
||||
// 授权端点
|
||||
app.get('/auth', async (c: Context) => {
|
||||
const query = c.req.query();
|
||||
|
||||
const authRequest: AuthorizationRequest = {
|
||||
response_type: query.response_type || '',
|
||||
client_id: query.client_id || '',
|
||||
redirect_uri: query.redirect_uri || '',
|
||||
scope: query.scope || '',
|
||||
state: query.state,
|
||||
nonce: query.nonce,
|
||||
code_challenge: query.code_challenge,
|
||||
code_challenge_method: query.code_challenge_method as 'plain' | 'S256' | undefined,
|
||||
prompt: query.prompt,
|
||||
max_age: query.max_age ? parseInt(query.max_age) : undefined,
|
||||
id_token_hint: query.id_token_hint,
|
||||
login_hint: query.login_hint,
|
||||
acr_values: query.acr_values,
|
||||
};
|
||||
|
||||
// 检查用户认证状态
|
||||
const userId = await authManager.getCurrentUser(c);
|
||||
if (!userId) {
|
||||
// 用户未认证,显示登录页面
|
||||
return await authManager.handleAuthenticationRequired(c, authRequest);
|
||||
}
|
||||
|
||||
// 用户已认证,处理授权请求
|
||||
const result = await provider.handleAuthorizationRequest(authRequest, userId);
|
||||
|
||||
if (!result.success) {
|
||||
const error = result.error;
|
||||
const errorParams = new URLSearchParams({
|
||||
error: error.error,
|
||||
...(error.error_description && { error_description: error.error_description }),
|
||||
...(error.error_uri && { error_uri: error.error_uri }),
|
||||
...(error.state && { state: error.state }),
|
||||
});
|
||||
|
||||
const redirectUri = result.redirectUri || query.redirect_uri;
|
||||
if (redirectUri) {
|
||||
return c.redirect(`${redirectUri}?${errorParams.toString()}`);
|
||||
} else {
|
||||
c.status(400);
|
||||
return c.json({
|
||||
error: error.error,
|
||||
error_description: error.error_description,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 成功生成授权码,重定向回客户端
|
||||
const params = new URLSearchParams({
|
||||
code: result.code,
|
||||
...(query.state && { state: query.state }),
|
||||
});
|
||||
|
||||
return c.redirect(`${result.redirectUri}?${params.toString()}`);
|
||||
return await provider.handleAuthorization(c);
|
||||
});
|
||||
|
||||
// 令牌端点
|
||||
app.post('/token', async (c: Context) => {
|
||||
const body = await c.req.formData();
|
||||
// 将可选字段的类型处理为可选的而不是undefined
|
||||
const clientId = body.get('client_id')?.toString();
|
||||
const tokenRequest: TokenRequest = {
|
||||
grant_type: body.get('grant_type')?.toString() || '',
|
||||
client_id: clientId || '',
|
||||
};
|
||||
// 可选参数,只有存在时才添加
|
||||
const code = body.get('code')?.toString();
|
||||
if (code) tokenRequest.code = code;
|
||||
|
||||
const redirectUri = body.get('redirect_uri')?.toString();
|
||||
if (redirectUri) tokenRequest.redirect_uri = redirectUri;
|
||||
|
||||
const clientSecret = body.get('client_secret')?.toString();
|
||||
if (clientSecret) tokenRequest.client_secret = clientSecret;
|
||||
|
||||
const refreshToken = body.get('refresh_token')?.toString();
|
||||
if (refreshToken) tokenRequest.refresh_token = refreshToken;
|
||||
|
||||
const codeVerifier = body.get('code_verifier')?.toString();
|
||||
if (codeVerifier) tokenRequest.code_verifier = codeVerifier;
|
||||
|
||||
const scope = body.get('scope')?.toString();
|
||||
if (scope) tokenRequest.scope = scope;
|
||||
|
||||
// 客户端认证
|
||||
const authHeader = c.req.header('Authorization');
|
||||
if (authHeader?.startsWith('Basic ')) {
|
||||
const decoded = atob(authHeader.substring(6));
|
||||
const [headerClientId, headerClientSecret] = decoded.split(':');
|
||||
if (headerClientId) {
|
||||
tokenRequest.client_id = headerClientId;
|
||||
}
|
||||
if (headerClientSecret) {
|
||||
tokenRequest.client_secret = headerClientSecret;
|
||||
}
|
||||
}
|
||||
|
||||
// 请求令牌
|
||||
const result = await provider.handleTokenRequest(tokenRequest);
|
||||
|
||||
if (!result.success) {
|
||||
c.status(400);
|
||||
return c.json({
|
||||
error: result.error.error,
|
||||
error_description: result.error.error_description,
|
||||
});
|
||||
}
|
||||
|
||||
return c.json(result.response);
|
||||
return await provider.handleToken(c);
|
||||
});
|
||||
|
||||
// 用户信息端点
|
||||
app.get('/userinfo', async (c: Context) => {
|
||||
const authHeader = c.req.header('Authorization');
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
c.status(401);
|
||||
c.header('WWW-Authenticate', 'Bearer');
|
||||
return c.json({
|
||||
error: 'invalid_token',
|
||||
error_description: '无效的访问令牌',
|
||||
});
|
||||
}
|
||||
|
||||
const accessToken = authHeader.substring(7);
|
||||
const result = await provider.getUserInfo(accessToken);
|
||||
|
||||
if (!result.success) {
|
||||
c.status(401);
|
||||
c.header('WWW-Authenticate', `Bearer error="${result.error.error}"`);
|
||||
return c.json({
|
||||
error: result.error.error,
|
||||
error_description: result.error.error_description,
|
||||
});
|
||||
}
|
||||
|
||||
return c.json(result.user);
|
||||
return await provider.handleUserInfo(c);
|
||||
});
|
||||
|
||||
// 令牌撤销端点
|
||||
app.post('/revoke', async (c: Context) => {
|
||||
const body = await c.req.formData();
|
||||
|
||||
const token = body.get('token')?.toString() || '';
|
||||
const tokenTypeHint = body.get('token_type_hint')?.toString();
|
||||
const clientId = body.get('client_id')?.toString();
|
||||
const clientSecret = body.get('client_secret')?.toString();
|
||||
|
||||
if (!token) {
|
||||
c.status(400);
|
||||
return c.json({
|
||||
error: 'invalid_request',
|
||||
error_description: '缺少token参数',
|
||||
});
|
||||
}
|
||||
|
||||
// 客户端认证
|
||||
let authClientId = clientId;
|
||||
let authClientSecret = clientSecret;
|
||||
|
||||
const authHeader = c.req.header('Authorization');
|
||||
if (authHeader?.startsWith('Basic ')) {
|
||||
const decoded = atob(authHeader.substring(6));
|
||||
const [id, secret] = decoded.split(':');
|
||||
authClientId = id;
|
||||
authClientSecret = secret;
|
||||
}
|
||||
|
||||
// 撤销令牌
|
||||
const result = await provider.revokeToken(token, tokenTypeHint);
|
||||
|
||||
if (!result.success && result.error) {
|
||||
c.status(400);
|
||||
return c.json({
|
||||
error: result.error.error,
|
||||
error_description: result.error.error_description,
|
||||
});
|
||||
}
|
||||
|
||||
// 撤销成功
|
||||
c.status(200);
|
||||
return c.body(null);
|
||||
return await provider.handleRevoke(c);
|
||||
});
|
||||
|
||||
// 令牌内省端点
|
||||
app.post('/introspect', async (c: Context) => {
|
||||
const body = await c.req.formData();
|
||||
|
||||
const token = body.get('token')?.toString() || '';
|
||||
const tokenTypeHint = body.get('token_type_hint')?.toString();
|
||||
const clientId = body.get('client_id')?.toString();
|
||||
const clientSecret = body.get('client_secret')?.toString();
|
||||
|
||||
if (!token) {
|
||||
c.status(400);
|
||||
return c.json({
|
||||
error: 'invalid_request',
|
||||
error_description: '缺少token参数',
|
||||
});
|
||||
}
|
||||
|
||||
// 客户端认证
|
||||
let authClientId = clientId;
|
||||
let authClientSecret = clientSecret;
|
||||
|
||||
const authHeader = c.req.header('Authorization');
|
||||
if (authHeader?.startsWith('Basic ')) {
|
||||
const decoded = atob(authHeader.substring(6));
|
||||
const [id, secret] = decoded.split(':');
|
||||
authClientId = id;
|
||||
authClientSecret = secret;
|
||||
}
|
||||
|
||||
// 内省令牌
|
||||
const result = await provider.introspectToken(token);
|
||||
|
||||
// 返回内省结果
|
||||
return c.json(result);
|
||||
return await provider.handleIntrospect(c);
|
||||
});
|
||||
|
||||
// 返回应用实例
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,24 @@ export interface OIDCProviderConfig {
|
|||
findUser: (userId: string) => Promise<OIDCUser | null>;
|
||||
/** 获取客户端的回调函数 */
|
||||
findClient: (clientId: string) => Promise<OIDCClient | null>;
|
||||
/** 认证配置选项 - 必须配置 */
|
||||
authConfig: {
|
||||
/** 密码验证器 - 用于验证用户名和密码,必须配置 */
|
||||
passwordValidator: PasswordValidator;
|
||||
/** 会话TTL(秒) */
|
||||
sessionTTL?: number;
|
||||
/** 页面配置 */
|
||||
pageConfig?: {
|
||||
/** 登录页面标题 */
|
||||
title?: string;
|
||||
/** 品牌名称 */
|
||||
brandName?: string;
|
||||
/** 品牌Logo URL */
|
||||
logoUrl?: string;
|
||||
};
|
||||
/** 记住我功能的最大时长(秒) */
|
||||
rememberMeMaxAge?: number;
|
||||
};
|
||||
/** 令牌过期时间配置 */
|
||||
tokenTTL?: {
|
||||
accessToken?: number; // 默认 3600 秒
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "@repo/tus",
|
||||
"version": "1.0.0",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: false,
|
||||
dts: true
|
||||
});
|
165
pnpm-lock.yaml
165
pnpm-lock.yaml
|
@ -47,12 +47,15 @@ importers:
|
|||
'@repo/oidc-provider':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/oidc-provider
|
||||
'@repo/storage':
|
||||
'@repo/tus':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/storage
|
||||
version: link:../../packages/tus
|
||||
'@trpc/server':
|
||||
specifier: 11.1.2
|
||||
version: 11.1.2(typescript@5.8.3)
|
||||
dayjs:
|
||||
specifier: ^1.11.12
|
||||
version: 1.11.13
|
||||
hono:
|
||||
specifier: ^4.7.10
|
||||
version: 4.7.10
|
||||
|
@ -77,6 +80,9 @@ importers:
|
|||
superjson:
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
transliteration:
|
||||
specifier: ^2.3.5
|
||||
version: 2.3.5
|
||||
valibot:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(typescript@5.8.3)
|
||||
|
@ -1281,72 +1287,85 @@ packages:
|
|||
resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.1.0':
|
||||
resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.1.0':
|
||||
resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.1.0':
|
||||
resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.1.0':
|
||||
resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.1.0':
|
||||
resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.1.0':
|
||||
resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.2':
|
||||
resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.34.2':
|
||||
resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.2':
|
||||
resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.34.2':
|
||||
resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.2':
|
||||
resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.2':
|
||||
resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.34.2':
|
||||
resolution: {integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==}
|
||||
|
@ -1434,24 +1453,28 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.3.2':
|
||||
resolution: {integrity: sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.3.2':
|
||||
resolution: {integrity: sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.3.2':
|
||||
resolution: {integrity: sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.3.2':
|
||||
resolution: {integrity: sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==}
|
||||
|
@ -1891,56 +1914,67 @@ packages:
|
|||
resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.41.0':
|
||||
resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.41.0':
|
||||
resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.41.0':
|
||||
resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.41.0':
|
||||
resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.41.0':
|
||||
resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.41.0':
|
||||
resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.41.0':
|
||||
resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.41.0':
|
||||
resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.41.0':
|
||||
resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.41.0':
|
||||
resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.41.0':
|
||||
resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==}
|
||||
|
@ -2277,24 +2311,28 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-arm64-musl@1.11.29':
|
||||
resolution: {integrity: sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-linux-x64-gnu@1.11.29':
|
||||
resolution: {integrity: sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-x64-musl@1.11.29':
|
||||
resolution: {integrity: sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-win32-arm64-msvc@1.11.29':
|
||||
resolution: {integrity: sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==}
|
||||
|
@ -2373,24 +2411,28 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.7':
|
||||
resolution: {integrity: sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.7':
|
||||
resolution: {integrity: sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.7':
|
||||
resolution: {integrity: sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.7':
|
||||
resolution: {integrity: sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==}
|
||||
|
@ -2896,9 +2938,6 @@ packages:
|
|||
buffer-crc32@0.2.13:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
|
@ -3063,9 +3102,6 @@ packages:
|
|||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||
engines: {node: '>=12.5.0'}
|
||||
|
||||
combine-errors@3.0.3:
|
||||
resolution: {integrity: sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -3179,9 +3215,6 @@ packages:
|
|||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
custom-error-instance@2.1.1:
|
||||
resolution: {integrity: sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==}
|
||||
|
||||
data-uri-to-buffer@6.0.2:
|
||||
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
@ -4122,9 +4155,6 @@ packages:
|
|||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
js-base64@3.7.7:
|
||||
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
|
@ -4225,24 +4255,28 @@ packages:
|
|||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.1:
|
||||
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.1:
|
||||
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.1:
|
||||
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.1:
|
||||
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
|
||||
|
@ -4275,24 +4309,6 @@ packages:
|
|||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lodash._baseiteratee@4.7.0:
|
||||
resolution: {integrity: sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==}
|
||||
|
||||
lodash._basetostring@4.12.0:
|
||||
resolution: {integrity: sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw==}
|
||||
|
||||
lodash._baseuniq@4.6.0:
|
||||
resolution: {integrity: sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A==}
|
||||
|
||||
lodash._createset@4.0.3:
|
||||
resolution: {integrity: sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA==}
|
||||
|
||||
lodash._root@3.0.1:
|
||||
resolution: {integrity: sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==}
|
||||
|
||||
lodash._stringtopath@4.8.0:
|
||||
resolution: {integrity: sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ==}
|
||||
|
||||
lodash.camelcase@4.3.0:
|
||||
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
|
||||
|
||||
|
@ -4315,9 +4331,6 @@ packages:
|
|||
lodash.throttle@4.1.1:
|
||||
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
|
||||
|
||||
lodash.uniqby@4.5.0:
|
||||
resolution: {integrity: sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
|
@ -4805,9 +4818,6 @@ packages:
|
|||
prop-types@15.8.1:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
|
||||
proper-lockfile@4.1.2:
|
||||
resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
|
||||
|
||||
proxy-agent@6.5.0:
|
||||
resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
|
||||
engines: {node: '>= 14'}
|
||||
|
@ -4827,9 +4837,6 @@ packages:
|
|||
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
querystringify@2.2.0:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
|
@ -4926,9 +4933,6 @@ packages:
|
|||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
requires-port@1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -4953,10 +4957,6 @@ packages:
|
|||
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
retry@0.12.0:
|
||||
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
reusify@1.1.0:
|
||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
|
@ -5497,10 +5497,6 @@ packages:
|
|||
resolution: {integrity: sha512-iHuaNcq5GZZnr3XDZNuu2LSyCzAOPwDuo5Qt+q64DfsTP1i3T2bKfxJhni2ZQxsvAoxRbuUK5QetJki4qc5aYA==}
|
||||
hasBin: true
|
||||
|
||||
tus-js-client@4.3.1:
|
||||
resolution: {integrity: sha512-ZLeYmjrkaU1fUsKbIi8JML52uAocjEZtBx4DKjRrqzrZa0O4MYwT6db+oqePlspV+FxXJAyFBc/L5gwUi2OFsg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tw-animate-css@1.3.0:
|
||||
resolution: {integrity: sha512-jrJ0XenzS9KVuDThJDvnhalbl4IYiMQ/XvpA0a2FL8KmlK+6CSMviO7ROY/I7z1NnUs5NnDhlM6fXmF40xPxzw==}
|
||||
|
||||
|
@ -5608,9 +5604,6 @@ packages:
|
|||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
url-parse@1.5.10:
|
||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
||||
|
||||
use-callback-ref@1.3.3:
|
||||
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -8448,8 +8441,6 @@ snapshots:
|
|||
|
||||
buffer-crc32@0.2.13: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
|
@ -8635,11 +8626,6 @@ snapshots:
|
|||
color-string: 1.9.1
|
||||
optional: true
|
||||
|
||||
combine-errors@3.0.3:
|
||||
dependencies:
|
||||
custom-error-instance: 2.1.1
|
||||
lodash.uniqby: 4.5.0
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
@ -8753,8 +8739,6 @@ snapshots:
|
|||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
custom-error-instance@2.1.1: {}
|
||||
|
||||
data-uri-to-buffer@6.0.2: {}
|
||||
|
||||
data-view-buffer@1.0.2:
|
||||
|
@ -9922,8 +9906,6 @@ snapshots:
|
|||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
js-base64@3.7.7: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
|
@ -10061,25 +10043,6 @@ snapshots:
|
|||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
|
||||
lodash._baseiteratee@4.7.0:
|
||||
dependencies:
|
||||
lodash._stringtopath: 4.8.0
|
||||
|
||||
lodash._basetostring@4.12.0: {}
|
||||
|
||||
lodash._baseuniq@4.6.0:
|
||||
dependencies:
|
||||
lodash._createset: 4.0.3
|
||||
lodash._root: 3.0.1
|
||||
|
||||
lodash._createset@4.0.3: {}
|
||||
|
||||
lodash._root@3.0.1: {}
|
||||
|
||||
lodash._stringtopath@4.8.0:
|
||||
dependencies:
|
||||
lodash._basetostring: 4.12.0
|
||||
|
||||
lodash.camelcase@4.3.0: {}
|
||||
|
||||
lodash.defaults@4.2.0: {}
|
||||
|
@ -10094,11 +10057,6 @@ snapshots:
|
|||
|
||||
lodash.throttle@4.1.1: {}
|
||||
|
||||
lodash.uniqby@4.5.0:
|
||||
dependencies:
|
||||
lodash._baseiteratee: 4.7.0
|
||||
lodash._baseuniq: 4.6.0
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
log-symbols@3.0.0:
|
||||
|
@ -10597,12 +10555,6 @@ snapshots:
|
|||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
proper-lockfile@4.1.2:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
retry: 0.12.0
|
||||
signal-exit: 3.0.7
|
||||
|
||||
proxy-agent@6.5.0:
|
||||
dependencies:
|
||||
agent-base: 7.1.3
|
||||
|
@ -10631,8 +10583,6 @@ snapshots:
|
|||
split-on-first: 1.1.0
|
||||
strict-uri-encode: 2.0.0
|
||||
|
||||
querystringify@2.2.0: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
quick-lru@7.0.1: {}
|
||||
|
@ -10736,8 +10686,6 @@ snapshots:
|
|||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
|
@ -10761,8 +10709,6 @@ snapshots:
|
|||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
|
||||
retry@0.12.0: {}
|
||||
|
||||
reusify@1.1.0: {}
|
||||
|
||||
rimraf@3.0.2:
|
||||
|
@ -11393,16 +11339,6 @@ snapshots:
|
|||
turbo-windows-64: 2.5.3
|
||||
turbo-windows-arm64: 2.5.3
|
||||
|
||||
tus-js-client@4.3.1:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
combine-errors: 3.0.3
|
||||
is-stream: 2.0.1
|
||||
js-base64: 3.7.7
|
||||
lodash.throttle: 4.1.1
|
||||
proper-lockfile: 4.1.2
|
||||
url-parse: 1.5.10
|
||||
|
||||
tw-animate-css@1.3.0: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
|
@ -11513,11 +11449,6 @@ snapshots:
|
|||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
url-parse@1.5.10:
|
||||
dependencies:
|
||||
querystringify: 2.2.0
|
||||
requires-port: 1.0.0
|
||||
|
||||
use-callback-ref@1.3.3(@types/react@19.1.5)(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
|
|
Loading…
Reference in New Issue