Files
sub2api/frontend/src/stores
feeeei 26e0a89323 人机验证增加阿里云验证码 2.0
沿用腾讯天御验证码引入的多服务商模型: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
2026-08-04 20:57:15 +08:00
..

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 user
  • token: string | null - JWT authentication token

Computed:

  • isAuthenticated: boolean - Whether user is currently authenticated

Actions:

  • login(credentials) - Authenticate user with username/password
  • register(userData) - Register new user account
  • logout() - Clear authentication and logout
  • checkAuth() - Restore session from localStorage
  • refreshUser() - 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 state
  • loading: boolean - Global loading state
  • toasts: Toast[] - Active toast notifications

Computed:

  • hasActiveToasts: boolean - Whether any toasts are active

Actions:

  • toggleSidebar() - Toggle sidebar state
  • setSidebarCollapsed(collapsed) - Set sidebar state explicitly
  • setLoading(isLoading) - Set loading state
  • showToast(type, message, duration?) - Show toast notification
  • showSuccess(message, duration?) - Show success toast
  • showError(message, duration?) - Show error toast
  • showInfo(message, duration?) - Show info toast
  • showWarning(message, duration?) - Show warning toast
  • hideToast(id) - Hide specific toast
  • clearAllToasts() - Clear all toasts
  • withLoading(operation) - Execute async operation with loading state
  • withLoadingAndError(operation, errorMessage?) - Execute with loading and error handling
  • reset() - 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
  • 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