沿用腾讯天御验证码引入的多服务商模型:aliyun_captcha_enabled 作为独立 开关,与 Cloudflare Turnstile、腾讯天御三方互斥(保存校验 + 运行时 CAPTCHA_PROVIDER_CONFLICT)。后台「安全与认证」合并为单张人机验证卡片: 总开关 + 服务商单选(Turnstile / 腾讯天御 / 阿里云),选中即启用该家并 关闭其它,落库仍是三个独立开关键,由前端映射保证互斥。 阿里云侧同时支持 aliyun 中国站与国际站(alibabacloud.com):两站前端脚本、 region 取值与服务端 API 完全一致,仅账号与 AccessKey 相互独立,因此由 「服务地域」决定线路即可——中国内地走 captcha.cn-shanghai.aliyuncs.com, 非中国内地(新加坡)走 captcha.ap-southeast-1.aliyuncs.com,AccessKey 取自持有该实例的账号,无需在配置中区分站点。 - AliyunCaptchaService 对称 TencentCaptchaService:服务端校验走官方 SDK VerifyIntelligentCaptcha,调用异常按 fail-closed 拦截,与 Turnstile 网络错误行为对称;保存设置时真实探测 AK/SK 有效性 - 保护面对齐腾讯扩展入口:VerifyTencentCaptchaIfEnabled 通用化为 VerifyActionCaptchaIfEnabled,OAuth 登录启动、passkey 登录在阿里云 启用时同样拦截;Turnstile 维持既有覆盖不扩大 - 前端 AliyunCaptchaWidget 为表单内预验证按钮(popup 模式),同时暴露 verify() 供 OAuth 启动、passkey 等动作入口程序化弹窗;未预验证直接 提交时弹窗兜底。SDK 按钮绑定异步完成,弹窗未出现前按 tick 重试触发, 并轮询弹窗可见性识别用户关闭 - captchaVerifyParam 复用 turnstile_token 请求字段提交;公开设置下发 aliyun_captcha_enabled / scene_id / prefix / region - CSP 放行验证码 CDN:script-src/style-src 加 *.alicdn.com
Pinia Stores Documentation
This directory contains all Pinia stores for the Sub2API frontend application.
Stores Overview
1. Auth Store (auth.ts)
Manages user authentication state, login/logout, and token persistence.
State:
user: User | null- Current authenticated usertoken: string | null- JWT authentication token
Computed:
isAuthenticated: boolean- Whether user is currently authenticated
Actions:
login(credentials)- Authenticate user with username/passwordregister(userData)- Register new user accountlogout()- Clear authentication and logoutcheckAuth()- Restore session from localStoragerefreshUser()- Fetch latest user data from server
2. App Store (app.ts)
Manages global UI state including sidebar, loading indicators, and toast notifications.
State:
sidebarCollapsed: boolean- Sidebar collapsed stateloading: boolean- Global loading statetoasts: Toast[]- Active toast notifications
Computed:
hasActiveToasts: boolean- Whether any toasts are active
Actions:
toggleSidebar()- Toggle sidebar statesetSidebarCollapsed(collapsed)- Set sidebar state explicitlysetLoading(isLoading)- Set loading stateshowToast(type, message, duration?)- Show toast notificationshowSuccess(message, duration?)- Show success toastshowError(message, duration?)- Show error toastshowInfo(message, duration?)- Show info toastshowWarning(message, duration?)- Show warning toasthideToast(id)- Hide specific toastclearAllToasts()- Clear all toastswithLoading(operation)- Execute async operation with loading statewithLoadingAndError(operation, errorMessage?)- Execute with loading and error handlingreset()- Reset store to defaults
Usage Examples
Auth Store
import { useAuthStore } from '@/stores'
// In component setup
const authStore = useAuthStore()
// Initialize on app startup
authStore.checkAuth()
// Login
try {
await authStore.login({ username: 'user', password: 'pass' })
console.log('Logged in:', authStore.user)
} catch (error) {
console.error('Login failed:', error)
}
// Check authentication
if (authStore.isAuthenticated) {
console.log('User is logged in:', authStore.user?.username)
}
// Logout
authStore.logout()
App Store
import { useAppStore } from '@/stores'
// In component setup
const appStore = useAppStore()
// Sidebar control
appStore.toggleSidebar()
appStore.setSidebarCollapsed(true)
// Loading state
appStore.setLoading(true)
// ... do work
appStore.setLoading(false)
// Or use helper
await appStore.withLoading(async () => {
const data = await fetchData()
return data
})
// Toast notifications
appStore.showSuccess('Operation completed!')
appStore.showError('Something went wrong!', 5000)
appStore.showInfo('FYI: This is informational')
appStore.showWarning('Be careful!')
// Custom toast
const toastId = appStore.showToast('info', 'Custom message', undefined) // No auto-dismiss
// Later...
appStore.hideToast(toastId)
Combined Usage in Vue Component
<script setup lang="ts">
import { useAuthStore, useAppStore } from '@/stores'
import { onMounted } from 'vue'
const authStore = useAuthStore()
const appStore = useAppStore()
onMounted(() => {
// Check for existing session
authStore.checkAuth()
})
async function handleLogin(username: string, password: string) {
try {
await appStore.withLoading(async () => {
await authStore.login({ username, password })
})
appStore.showSuccess('Welcome back!')
} catch (error) {
appStore.showError('Login failed. Please check your credentials.')
}
}
async function handleLogout() {
authStore.logout()
appStore.showInfo('You have been logged out.')
}
</script>
<template>
<div>
<button @click="appStore.toggleSidebar">Toggle Sidebar</button>
<div v-if="appStore.loading">Loading...</div>
<div v-if="authStore.isAuthenticated">
Welcome, {{ authStore.user?.username }}!
<button @click="handleLogout">Logout</button>
</div>
<div v-else>
<button @click="handleLogin('user', 'pass')">Login</button>
</div>
</div>
</template>
Persistence
- Auth Store: Token and user data are automatically persisted to
localStorage- Keys:
auth_token,auth_user - Restored on
checkAuth()call
- Keys:
- App Store: No persistence (UI state resets on page reload)
TypeScript Support
All stores are fully typed with TypeScript. Import types from @/types:
import type { User, Toast, ToastType } from '@/types'
Testing
Stores can be reset to initial state:
// Auth store
authStore.logout() // Clears all auth state
// App store
appStore.reset() // Resets to defaults