From 1aa6f5672165253e15c74faa62941d9cd50aff6f Mon Sep 17 00:00:00 2001 From: LiuShen <3162475700@qq.com> Date: Tue, 16 Sep 2025 13:48:31 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=A3=E6=B7=BB=E5=8A=A0=E5=BA=94?= =?UTF-8?q?=E7=94=A8tinyauth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tinyauth/3.6.2/data.yml | 210 ++++++++++++++++++++++++++++++ tinyauth/3.6.2/docker-compose.yml | 42 ++++++ tinyauth/README.md | 78 +++++++++++ tinyauth/data.yml | 32 +++++ tinyauth/logo.png | Bin 0 -> 18925 bytes 5 files changed, 362 insertions(+) create mode 100644 tinyauth/3.6.2/data.yml create mode 100644 tinyauth/3.6.2/docker-compose.yml create mode 100644 tinyauth/README.md create mode 100644 tinyauth/data.yml create mode 100644 tinyauth/logo.png diff --git a/tinyauth/3.6.2/data.yml b/tinyauth/3.6.2/data.yml new file mode 100644 index 000000000..2638871fa --- /dev/null +++ b/tinyauth/3.6.2/data.yml @@ -0,0 +1,210 @@ +additionalProperties: + formFields: + - default: 14389 + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + ja: ポート + ms: Port + pt-br: Porta + ru: Порт + ko: 포트 + zh: HTTP 端口 + zh-Hant: HTTP 連接埠 + required: true + rule: paramPort + type: number + + - default: "" + envKey: PANEL_TINYAUTH_APP_URL + labelEn: App URL + labelZh: 应用地址 + label: + en: App URL + ja: アプリURL + ms: URL Aplikasi + pt-br: URL do Aplicativo + ru: URL приложения + ko: 앱 URL + zh: 应用地址 + zh-Hant: 應用地址 + required: false + type: text + + - default: "" + envKey: PANEL_TINYAUTH_SECRET + labelEn: Secret + labelZh: 密钥 + label: + en: Secret + ja: シークレット + ms: Rahsia + pt-br: Segredo + ru: Секрет + ko: 비밀 + zh: 密钥 + zh-Hant: 密鑰 + required: false + type: password + + - default: "86400" + envKey: PANEL_TINYAUTH_SESSION_EXPIRY + labelEn: Session Expiry (seconds) + labelZh: 会话过期时间(秒) + label: + en: Session Expiry (seconds) + ja: セッション有効期限(秒) + ms: Tamat Sesi (saat) + pt-br: Expiração da Sessão (segundos) + ru: Время жизни сессии (секунды) + ko: 세션 만료 (초) + zh: 会话过期时间(秒) + zh-Hant: 會話過期時間(秒) + required: false + type: number + + - default: "TinyAuth" + envKey: PANEL_TINYAUTH_APP_TITLE + labelEn: App Title + labelZh: 应用标题 + label: + en: App Title + ja: アプリタイトル + ms: Tajuk Aplikasi + pt-br: Título do Aplicativo + ru: Заголовок приложения + ko: 앱 제목 + zh: 应用标题 + zh-Hant: 應用標題 + required: false + type: text + + - default: "" + envKey: PANEL_TINYAUTH_OAUTH_AUTO_REDIRECT + labelEn: Auto Redirect Provider + labelZh: 自动跳转提供商 + label: + en: Auto Redirect Provider + ja: 自動リダイレクトプロバイダー + ms: Penyedia Pengalihan Automatik + pt-br: Provedor de Redirecionamento Automático + ru: Автоматический редирект провайдера + ko: 자동 리디렉션 제공자 + zh: 自动跳转提供商 + zh-Hant: 自動跳轉提供商 + required: false + type: select + values: + - label: None + value: "none" + - label: GitHub + value: "github" + - label: Google + value: "google" + - label: Generic + value: "generic" + + - default: "" + envKey: PANEL_TINYAUTH_BACKGROUND_IMAGE + labelEn: Background Image + labelZh: 背景图片 + label: + en: Background Image + ja: 背景画像 + ms: Imej Latar Belakang + pt-br: Imagem de Fundo + ru: Фоновое изображение + ko: 배경 이미지 + zh: 背景图片 + zh-Hant: 背景圖片 + required: false + type: text + + # GitHub + - default: "" + envKey: PANEL_TINYAUTH_GITHUB_CLIENT_ID + labelEn: GitHub Client ID + labelZh: GitHub 客户端ID + required: false + type: text + + - default: "" + envKey: PANEL_TINYAUTH_GITHUB_CLIENT_SECRET + labelEn: GitHub Client Secret + labelZh: GitHub 客户端密钥 + required: false + type: password + + - default: "" + envKey: PANEL_TINYAUTH_GITHUB_REDIRECT_URI + labelEn: GitHub Redirect URI + labelZh: GitHub 回调地址 + required: false + type: text + + # Google + - default: "" + envKey: PANEL_TINYAUTH_GOOGLE_CLIENT_ID + labelEn: Google Client ID + labelZh: Google 客户端ID + required: false + type: text + + - default: "" + envKey: PANEL_TINYAUTH_GOOGLE_CLIENT_SECRET + labelEn: Google Client Secret + labelZh: Google 客户端密钥 + required: false + type: password + + - default: "" + envKey: PANEL_TINYAUTH_GOOGLE_REDIRECT_URI + labelEn: Google Redirect URI + labelZh: Google 回调地址 + required: false + type: text + + # Generic + - default: "" + envKey: PANEL_TINYAUTH_GENERIC_NAME + labelEn: Generic Provider Name + labelZh: 通用提供商名称 + required: false + type: text + + - default: "" + envKey: PANEL_TINYAUTH_GENERIC_CLIENT_ID + labelEn: Generic Client ID + labelZh: 通用客户端ID + required: false + type: text + + - default: "" + envKey: PANEL_TINYAUTH_GENERIC_CLIENT_SECRET + labelEn: Generic Client Secret + labelZh: 通用客户端密钥 + required: false + type: password + + - default: "" + envKey: PANEL_TINYAUTH_GENERIC_AUTH_URL + labelEn: Generic Auth URL + labelZh: 通用认证地址 + required: false + type: text + + - default: "" + envKey: PANEL_TINYAUTH_GENERIC_TOKEN_URL + labelEn: Generic Token URL + labelZh: 通用令牌地址 + required: false + type: text + + - default: "" + envKey: PANEL_TINYAUTH_GENERIC_USER_URL + labelEn: Generic User Info URL + labelZh: 通用用户信息地址 + required: false + type: text diff --git a/tinyauth/3.6.2/docker-compose.yml b/tinyauth/3.6.2/docker-compose.yml new file mode 100644 index 000000000..72e888142 --- /dev/null +++ b/tinyauth/3.6.2/docker-compose.yml @@ -0,0 +1,42 @@ +services: + tinyauth: + image: ghcr.io/tinyauth/tinyauth:v3.6.2 + container_name: ${CONTAINER_NAME} + environment: + # ---- 通用配置 ---- + - APP_URL=${PANEL_TINYAUTH_APP_URL} + - SECRET=${PANEL_TINYAUTH_SECRET} + - SESSION_EXPIRY=${PANEL_TINYAUTH_SESSION_EXPIRY} + - APP_TITLE=${PANEL_TINYAUTH_APP_TITLE} + - OAUTH_AUTO_REDIRECT=${PANEL_TINYAUTH_OAUTH_AUTO_REDIRECT} + - BACKGROUND_IMAGE=${PANEL_TINYAUTH_BACKGROUND_IMAGE} + + # ---- GitHub ---- + - GITHUB_CLIENT_ID=${PANEL_TINYAUTH_GITHUB_CLIENT_ID} + - GITHUB_CLIENT_SECRET=${PANEL_TINYAUTH_GITHUB_CLIENT_SECRET} + - GITHUB_REDIRECT_URI=${PANEL_TINYAUTH_GITHUB_REDIRECT_URI} + + # ---- Google ---- + - GOOGLE_CLIENT_ID=${PANEL_TINYAUTH_GOOGLE_CLIENT_ID} + - GOOGLE_CLIENT_SECRET=${PANEL_TINYAUTH_GOOGLE_CLIENT_SECRET} + - GOOGLE_REDIRECT_URI=${PANEL_TINYAUTH_GOOGLE_REDIRECT_URI} + + # ---- Generic (示例 Linux.Do) ---- + - GENERIC_NAME=${PANEL_TINYAUTH_GENERIC_NAME} + - GENERIC_CLIENT_ID=${PANEL_TINYAUTH_GENERIC_CLIENT_ID} + - GENERIC_CLIENT_SECRET=${PANEL_TINYAUTH_GENERIC_CLIENT_SECRET} + - GENERIC_AUTH_URL=${PANEL_TINYAUTH_GENERIC_AUTH_URL} + - GENERIC_TOKEN_URL=${PANEL_TINYAUTH_GENERIC_TOKEN_URL} + - GENERIC_USER_URL=${PANEL_TINYAUTH_GENERIC_USER_URL} + + ports: + - "${PANEL_APP_PORT_HTTP}:3000" + labels: + createdBy: "Apps" + networks: + - 1panel-network + restart: always + +networks: + 1panel-network: + external: true diff --git a/tinyauth/README.md b/tinyauth/README.md new file mode 100644 index 000000000..abc31b5f0 --- /dev/null +++ b/tinyauth/README.md @@ -0,0 +1,78 @@ +# TinyAuth + +TinyAuth 是一个轻量级的 OAuth2 / OIDC 认证代理,适合为已有的 Web 应用、面板或服务添加登录保护。你可以将它部署在应用之前,通过标准的身份提供商(如 Casdoor、Keycloak、Auth0 等)实现单点登录(SSO)。 + +## 功能特性 + +* 支持 OAuth2 / OpenID Connect 协议 +* 支持多身份提供商配置 +* 可保护现有 Web 服务(无需修改应用本身) +* 简单的反向代理模式 +* 支持多种回调和重定向策略 + +## 快速开始 + +### 1. 部署 TinyAuth + +使用 Docker 运行: + +```bash +docker run -d \ + --name=tinyauth \ + -p 8080:8080 \ + -v $(pwd)/config.yml:/app/config.yml \ + ghcr.io/tinyauth/tinyauth:latest +``` + +### 2. 配置示例 + +在 `config.yml` 中配置认证提供商(例如 Casdoor): + +```yaml +server: + port: 8080 + +providers: + - id: casdoor + name: Casdoor + client_id: your-client-id + client_secret: your-client-secret + issuer: https://auth.example.com + redirect_url: https://tinyauth.example.com/callback +``` + +### 3. Nginx 反向代理示例 + +```nginx +server { + listen 80; + server_name app.example.com; + + location / { + proxy_pass http://127.0.0.1:8080; + } +} +``` + +### 4. 登录流程 + +1. 访问受保护的应用(如 `app.example.com`) +2. TinyAuth 会跳转到认证提供商(如 Casdoor)登录 +3. 登录成功后跳转回应用 + +## 配置参数 + +常见参数: + +| 参数 | 描述 | +| --------------- | ----------------- | +| `client_id` | OAuth2 客户端 ID | +| `client_secret` | OAuth2 客户端密钥 | +| `issuer` | 身份提供商地址(OIDC 发行者) | +| `redirect_url` | 登录成功后的回调地址 | + +## 参考链接 + +* [TinyAuth 文档](https://tinyauth.app/docs) +* [OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/)(类似工具) +* [Casdoor](https://casdoor.org/)(身份提供商示例) \ No newline at end of file diff --git a/tinyauth/data.yml b/tinyauth/data.yml new file mode 100644 index 000000000..6aff02759 --- /dev/null +++ b/tinyauth/data.yml @@ -0,0 +1,32 @@ +name: TinyAuth +tags: + - 开发工具 + - 安全 +title: 轻量级OAuth2/OIDC认证代理,支持多身份提供商的统一登录解决方案 +description: 一个轻量级的OAuth2和OIDC认证代理工具,常用于在现有Web服务前添加统一登录认证。 +additionalProperties: + key: tinyauth + name: TinyAuth + tags: + - DevTool + - Security + shortDescZh: 轻量级OAuth2/OIDC认证代理,支持多身份提供商的一站式登录认证 + shortDescEn: Lightweight OAuth2/OIDC authentication proxy with multi-provider SSO support + type: website + crossVersionUpdate: true + limit: 0 + website: https://tinyauth.app/ + github: https://github.com/tinyauth/tinyauth + document: https://tinyauth.app/docs/ + description: + en: A lightweight reverse proxy and authentication middleware supporting multiple OAuth2 and OpenID Connect providers, ideal for securing existing web services with SSO. + zh: 一个轻量级的反向代理与认证中间件,支持多种OAuth2和OIDC身份提供商,为现有Web服务提供统一登录保护。 + zh-Hant: 一個輕量級的反向代理與認證中介軟體,支援多種OAuth2和OIDC身份提供商,為既有Web服務提供單一登入保護。 + ja: 複数のOAuth2およびOIDCプロバイダーに対応した軽量認証プロキシで、既存のWebサービスにSSO保護を追加可能。 + ms: Proksi pengesahan ringan yang menyokong penyedia OAuth2 dan OIDC pelbagai, sesuai untuk melindungi perkhidmatan web sedia ada dengan SSO. + pt-br: Proxy de autenticação leve que suporta múltiplos provedores OAuth2 e OIDC, adequado para proteger serviços web existentes com SSO. + ru: Легковесный обратный прокси и провайдер аутентификации, поддерживающий OAuth2 и OIDC, обеспечивающий единую точку входа для существующих веб-сервисов. + ko: 다양한 OAuth2 및 OIDC 제공자를 지원하는 경량 인증 프록시로, 기존 웹 서비스에 단일 로그인 보호를 제공합니다. + architectures: + - amd64 + - arm64 diff --git a/tinyauth/logo.png b/tinyauth/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..32a0d2b268b264409636e785c582785e94fdca46 GIT binary patch literal 18925 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 literal 0 HcmV?d00001