feat: add browser environment support with access token authentication

Enable the web client to run in plain browser environments alongside Telegram Mini App support:

- Server: Add CLI_API_TOKEN authentication path as alternative to Telegram initData
- Client: Add useAuthSource hook to detect and manage Telegram vs browser auth sources
- Client: Add usePlatform hook for platform abstraction with graceful haptic feedback degradation
- Client: Add LoginPrompt component for browser access token login
- Client: Extend useTheme to fall back to system prefers-color-scheme in browser
- Client: Migrate all direct HapticFeedback calls to use usePlatform hook
This commit is contained in:
weishu
2025-12-18 12:45:49 +08:00
parent 5fe1256e11
commit cf2b96b566
14 changed files with 552 additions and 96 deletions
+40 -15
View File
@@ -5,10 +5,16 @@ import { configuration } from '../../configuration'
import { validateTelegramInitData } from '../telegramInitData'
import type { WebAppEnv } from '../middleware/auth'
const authBodySchema = z.object({
const telegramAuthSchema = z.object({
initData: z.string()
})
const accessTokenAuthSchema = z.object({
accessToken: z.string()
})
const authBodySchema = z.union([telegramAuthSchema, accessTokenAuthSchema])
export function createAuthRoutes(jwtSecret: Uint8Array): Hono<WebAppEnv> {
const app = new Hono<WebAppEnv>()
@@ -19,18 +25,37 @@ export function createAuthRoutes(jwtSecret: Uint8Array): Hono<WebAppEnv> {
return c.json({ error: 'Invalid body' }, 400)
}
const initData = parsed.data.initData
const result = validateTelegramInitData(initData, configuration.telegramBotToken)
if (!result.ok) {
return c.json({ error: result.error }, 401)
let userId: number
let username: string | undefined
let firstName: string | undefined
let lastName: string | undefined
// Access Token authentication (CLI_API_TOKEN)
if ('accessToken' in parsed.data) {
if (parsed.data.accessToken !== configuration.cliApiToken) {
return c.json({ error: 'Invalid access token' }, 401)
}
// Use first allowed chat ID as the shared user identity
userId = configuration.allowedChatIds[0]
firstName = 'Web User'
} else {
// Telegram initData authentication
const result = validateTelegramInitData(parsed.data.initData, configuration.telegramBotToken)
if (!result.ok) {
return c.json({ error: result.error }, 401)
}
userId = result.user.id
if (!configuration.isChatIdAllowed(userId)) {
return c.json({ error: 'User not allowed' }, 403)
}
username = result.user.username
firstName = result.user.first_name
lastName = result.user.last_name
}
const telegramUserId = result.user.id
if (!configuration.isChatIdAllowed(telegramUserId)) {
return c.json({ error: 'User not allowed' }, 403)
}
const token = await new SignJWT({ uid: telegramUserId })
const token = await new SignJWT({ uid: userId })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('15m')
@@ -39,10 +64,10 @@ export function createAuthRoutes(jwtSecret: Uint8Array): Hono<WebAppEnv> {
return c.json({
token,
user: {
id: telegramUserId,
username: result.user.username,
firstName: result.user.first_name,
lastName: result.user.last_name
id: userId,
username,
firstName,
lastName
}
})
})