feat(auth): support shared session tokens and device/node pairing integration

This commit is contained in:
Haitao Pan 2026-01-30 23:12:00 +08:00
parent 6b85280b3d
commit 01c879c143
4 changed files with 167 additions and 1 deletions

View File

@ -74,6 +74,12 @@ yarn dev
yarn build
```
## 认证配置 (Authentication Configuration)
有关如何配置 GitHub 和 Google OIDC 认证的详细步骤,请参阅 [OIDC 认证指南](./docs/integrations/oidc-auth.md)。
> For detailed steps on configuring GitHub and Google OIDC authentication, please refer to the [OIDC Authentication Guide](./docs/integrations/oidc-auth.md).
## 开发指南 (Development Guidelines)
有关详细的编码标准、架构规则和 Agent 特定说明,请参阅 [AGENTS.md](./AGENTS.md)。

View File

@ -0,0 +1,88 @@
# OIDC Authentication Configuration Guide
This guide describes how to configure GitHub and Google OIDC authentication for the Cloud Neutral Toolkit.
## Prerequisites
- Access to GitHub Developer Settings.
- Access to Google Cloud Console.
- Properly configured `accounts.svc.plus` and `console.svc.plus` services.
---
## 1. GitHub Configuration
### 1.1 Create GitHub OAuth App
1. Log in to GitHub and go to **Settings** > **Developer Settings** > **OAuth Apps**.
2. Click **New OAuth App**.
3. **Application name**: e.g., `Cloud Neutral Console`
4. **Homepage URL**: `https://console.svc.plus` (or your actual console domain)
5. **Authorization callback URL**: `https://accounts.svc.plus/api/auth/oauth/callback/github`
6. Click **Register application**.
7. Copy the **Client ID**.
8. Click **Generate a new client secret** and copy the **Client Secret**.
### 1.2 Configure Environment Variables
Set the following environment variables for **accounts.svc.plus**:
```bash
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
# Optional: if you want to override the default callback
# GITHUB_REDIRECT_URL=https://accounts.svc.plus/api/auth/oauth/callback/github
```
---
## 2. Google Configuration
### 2.1 Create Google OAuth Client ID
1. Log in to [Google Cloud Console](https://console.cloud.google.com/).
2. Select or create a project.
3. Go to **APIs & Services** > **Credentials**.
4. Click **Create Credentials** > **OAuth client ID**.
5. **Application type**: `Web application`.
6. **Name**: e.g., `Cloud Neutral Console`.
7. **Authorized JavaScript origins**:
- `https://console.svc.plus`
8. **Authorized redirect URIs**:
- `https://accounts.svc.plus/api/auth/oauth/callback/google`
9. Click **Create**.
10. Copy the **Client ID** and **Client Secret**.
### 2.2 Configure Environment Variables
Set the following environment variables for **accounts.svc.plus**:
```bash
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
# Optional: if you want to override the default callback
# GOOGLE_REDIRECT_URL=https://accounts.svc.plus/api/auth/oauth/callback/google
```
---
## 3. General OIDC Environment Variables
Ensure these are also set for **accounts.svc.plus**:
```bash
OAUTH_REDIRECT_URL=https://accounts.svc.plus/api/auth/oauth/callback
OAUTH_FRONTEND_URL=https://console.svc.plus
```
**Note**: The backend automatically appends `/{provider}` to `OAUTH_REDIRECT_URL` if a provider-specific redirect URL is not provided.
---
## 4. Frontend Configuration
For **console.svc.plus**, ensure the following is set so it knows where to redirect for the initial OAuth step:
```bash
NEXT_PUBLIC_ACCOUNTS_SVC_URL=https://accounts.svc.plus
```

View File

@ -0,0 +1,48 @@
import { NextRequest, NextResponse } from 'next/server'
import { applySessionCookie, deriveMaxAgeFromExpires } from '@lib/authGateway'
import { getAccountServiceApiBaseUrl } from '@server/serviceConfig'
const ACCOUNT_API_BASE = getAccountServiceApiBaseUrl()
export async function POST(request: NextRequest) {
try {
const payload = await request.json()
const { publicToken, userId, email, role } = payload
if (!publicToken || !userId || !email) {
return NextResponse.json({ success: false, error: 'invalid_request' }, { status: 400 })
}
const response = await fetch(`${ACCOUNT_API_BASE}/token/exchange`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
public_token: publicToken,
user_id: userId,
email,
roles: role,
}),
cache: 'no-store',
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return NextResponse.json({ success: false, error: errorData.error || 'exchange_failed' }, { status: response.status })
}
const data = await response.json()
const { access_token, expires_in } = data
const result = NextResponse.json({ success: true })
// If backend returns expires_in (seconds), use it; otherwise derive from expiresAt if it exists
const maxAge = typeof expires_in === 'number' ? expires_in : deriveMaxAgeFromExpires(data.expiresAt)
applySessionCookie(result, access_token, maxAge)
return result
} catch (error) {
console.error('Token exchange proxy failed', error)
return NextResponse.json({ success: false, error: 'internal_error' }, { status: 500 })
}
}

View File

@ -57,17 +57,41 @@ function shouldUseSecureCookies(): boolean {
const secureCookieBase = {
httpOnly: true,
secure: shouldUseSecureCookies(),
sameSite: 'strict' as const,
sameSite: 'lax' as const, // Change to lax to support cross-subdomain
path: '/',
}
/**
* Resolves the cookie domain based on the current environment.
* If running on a .svc.plus subdomain, returns '.svc.plus' to allow SSO.
*/
function resolveCookieDomain(): string | undefined {
if (typeof window !== 'undefined') {
const host = window.location.hostname
if (host.endsWith('.svc.plus')) {
return '.svc.plus'
}
}
// For server-side, check headers or environment
const baseUrl = process.env.NEXT_PUBLIC_APP_BASE_URL || process.env.APP_BASE_URL || ''
if (baseUrl.includes('.svc.plus')) {
return '.svc.plus'
}
return undefined
}
export function applySessionCookie(response: NextResponse, token: string, maxAge?: number) {
const resolvedMaxAge = Number.isFinite(maxAge) && maxAge && maxAge > 0 ? Math.floor(maxAge) : SESSION_DEFAULT_MAX_AGE
const domain = resolveCookieDomain()
response.cookies.set({
name: SESSION_COOKIE_NAME,
value: token,
...secureCookieBase,
maxAge: resolvedMaxAge,
...(domain ? { domain } : {}),
})
}