删除 android/、ios/ 两个 Capacitor 原生工程与 capacitor.config.ts、 ionic.config.json,以及 scripts/android、scripts/ios.sh 构建脚本; 根与 apps/web 的 @capacitor/* 依赖和相关 npm scripts 一并移除。 Web 侧去掉原生分支:登录只走云乐坊容器授权面板或顶层重定向, 暗色模式不再改原生状态栏,移除 Android 物理返回键处理。 保留 @nuxtjs/ionic(Web UI 框架)、云乐坊 window.ylf 桥接与 PWA。 决策记录见 docs/decisions/0004-web-only.md。
115 lines
2.8 KiB
TypeScript
115 lines
2.8 KiB
TypeScript
import type { RedirectSsoOptions, SsoCodeExchangeOptions } from '@yunlefun/sso'
|
|
import {
|
|
readSsoClientId,
|
|
readSsoRedirectUri,
|
|
readSsoScope,
|
|
} from '@yunlefun/sso'
|
|
|
|
export interface YunlefunSsoConfigInput {
|
|
clientId?: string
|
|
cloudbaseEnv?: string
|
|
exchangeUrl?: string
|
|
redirectUri?: string
|
|
redirectUris?: string | string[]
|
|
scope?: string
|
|
ssoOrigin?: string
|
|
}
|
|
|
|
export interface YunlefunSsoConfigContext {
|
|
currentOrigin: string
|
|
}
|
|
|
|
export interface YunlefunSsoConfig {
|
|
cloudbaseEnv: string
|
|
exchange: SsoCodeExchangeOptions
|
|
redirect: RedirectSsoOptions
|
|
}
|
|
|
|
export type YunlefunSsoConfigResult
|
|
= | { ok: true, config: YunlefunSsoConfig }
|
|
| { ok: false, message: string }
|
|
|
|
const CLOUDBASE_ENV_RE = /^[a-z0-9][a-z0-9-]{0,63}$/i
|
|
const TRAILING_SLASH_RE = /\/+$/
|
|
|
|
export function resolveYunlefunSsoConfig(
|
|
input: YunlefunSsoConfigInput,
|
|
context: YunlefunSsoConfigContext,
|
|
): YunlefunSsoConfigResult {
|
|
const clientId = readSsoClientId(input.clientId)
|
|
const cloudbaseEnv = normalizeValue(input.cloudbaseEnv)
|
|
const exchangeUrl = readSsoRedirectUri(input.exchangeUrl)
|
|
const webRedirectUris = readWebRedirectUris(input.redirectUris ?? input.redirectUri)
|
|
const redirectUri = webRedirectUris.find(uri => new URL(uri).origin === context.currentOrigin)
|
|
const scope = readSsoScope(input.scope)
|
|
const ssoOrigin = normalizeHttpsOrigin(input.ssoOrigin)
|
|
|
|
if (!clientId
|
|
|| !CLOUDBASE_ENV_RE.test(cloudbaseEnv)
|
|
|| !exchangeUrl
|
|
|| webRedirectUris.length === 0
|
|
|| scope.length === 0
|
|
|| !ssoOrigin) {
|
|
return {
|
|
ok: false,
|
|
message: '登录服务尚未完成 SSO v3 配置',
|
|
}
|
|
}
|
|
|
|
if (!redirectUri) {
|
|
return {
|
|
ok: false,
|
|
message: '当前域名尚未登记为登录回跳地址',
|
|
}
|
|
}
|
|
|
|
return {
|
|
ok: true,
|
|
config: {
|
|
cloudbaseEnv,
|
|
exchange: { exchangeUrl },
|
|
redirect: {
|
|
clientId,
|
|
redirectUri,
|
|
scope,
|
|
ssoOrigin,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
function readWebRedirectUris(value: string | string[] | undefined): string[] {
|
|
const candidates = Array.isArray(value)
|
|
? value
|
|
: typeof value === 'string'
|
|
? value.split(/[\n,]/)
|
|
: []
|
|
|
|
return [...new Set(candidates
|
|
.map(candidate => readSsoRedirectUri(candidate.trim()))
|
|
.filter((candidate): candidate is string => Boolean(candidate)))]
|
|
}
|
|
|
|
function normalizeHttpsOrigin(value: string | undefined): string {
|
|
const normalized = normalizeValue(value).replace(TRAILING_SLASH_RE, '')
|
|
if (!normalized)
|
|
return ''
|
|
|
|
try {
|
|
const url = new URL(normalized)
|
|
return url.protocol === 'https:'
|
|
&& !url.username
|
|
&& !url.password
|
|
&& url.origin === normalized
|
|
? url.origin
|
|
: ''
|
|
}
|
|
catch {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
function normalizeValue(value: string | undefined): string {
|
|
return typeof value === 'string' ? value.trim() : ''
|
|
}
|