feat(admin-ui): update admin UI URLs to remove trailing slashes and adjust redirects
This commit is contained in:
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
environment:
|
||||
name: Admin Production
|
||||
url: https://admin.airi.build/admin/
|
||||
url: https://admin.airi.build/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -140,7 +140,7 @@ jobs:
|
||||
|
||||
environment:
|
||||
name: AdminServerDev
|
||||
url: https://server-dev.airi-server-admin.pages.dev/admin/
|
||||
url: https://server-dev.airi-server-admin.pages.dev/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -176,4 +176,4 @@ jobs:
|
||||
|
||||
- name: Print preview URL
|
||||
run: |
|
||||
echo "Preview URL: https://server-dev.airi-server-admin.pages.dev/admin/"
|
||||
echo "Preview URL: https://server-dev.airi-server-admin.pages.dev/"
|
||||
|
||||
@@ -42,7 +42,7 @@ Set this when previewing or deploying auth UI to a different Cloudflare URL.
|
||||
|
||||
Default:
|
||||
|
||||
`ADMIN_UI_URL=https://admin.airi.build/admin`
|
||||
`ADMIN_UI_URL=https://admin.airi.build`
|
||||
|
||||
Set this when previewing or deploying admin UI to a different Cloudflare URL.
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ const EnvSchema = object({
|
||||
// Standalone admin UI base URL. The server keeps `/admin/*` as the historical
|
||||
// entrypoint and redirects those requests here after ui-admin moved out of
|
||||
// the server image.
|
||||
ADMIN_UI_URL: optional(string(), 'https://admin.airi.build/admin'),
|
||||
ADMIN_UI_URL: optional(string(), 'https://admin.airi.build'),
|
||||
|
||||
// Canonical user-facing web app origin. Used as the Stripe redirect base
|
||||
// (success_url / cancel_url / portal return_url) when a request has no trusted
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { ensureDynamicFirstPartyRedirectUri, seedTrustedClients } from '../auth'
|
||||
import { createAuth, ensureDynamicFirstPartyRedirectUri, seedTrustedClients } from '../auth'
|
||||
|
||||
function createMockDb(existingRowsByCall: unknown[][] = []) {
|
||||
const limit = vi.fn()
|
||||
@@ -29,6 +29,22 @@ function createMockDb(existingRowsByCall: unknown[][] = []) {
|
||||
return { db, limit, values, capturedValues }
|
||||
}
|
||||
|
||||
describe('createAuth', () => {
|
||||
it('allows signed-in users to link OAuth accounts that use a different email', () => {
|
||||
const auth = createAuth({} as any, {
|
||||
API_SERVER_URL: 'http://localhost:3000',
|
||||
AUTH_GOOGLE_CLIENT_ID: 'google-client',
|
||||
AUTH_GOOGLE_CLIENT_SECRET: 'google-secret',
|
||||
AUTH_GITHUB_CLIENT_ID: 'github-client',
|
||||
AUTH_GITHUB_CLIENT_SECRET: 'github-secret',
|
||||
BETTER_AUTH_SECRET: 'test-secret-test-secret-test-secret',
|
||||
ADDITIONAL_TRUSTED_ORIGINS: [],
|
||||
} as any)
|
||||
|
||||
expect(auth.options.account?.accountLinking?.allowDifferentEmails).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('seedTrustedClients', () => {
|
||||
it('seeds trusted first-party clients with explicit oauth metadata', async () => {
|
||||
const { db, values, capturedValues } = createMockDb([[], [], []])
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('parseEnv', () => {
|
||||
expect(env.DATABASE_URL).toBe('postgres://example')
|
||||
expect(env.REDIS_URL).toBe('redis://example')
|
||||
expect(env.AUTH_UI_URL).toBe('https://auth.airi.build/ui')
|
||||
expect(env.ADMIN_UI_URL).toBe('https://admin.airi.build/admin')
|
||||
expect(env.ADMIN_UI_URL).toBe('https://admin.airi.build')
|
||||
expect(env.ADDITIONAL_TRUSTED_ORIGINS).toEqual([])
|
||||
})
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('auth UI routes', () => {
|
||||
expect(res.status).toBe(302)
|
||||
|
||||
const location = res.headers.get('location')
|
||||
expect(location).toBe('https://auth.airi.build/ui/sign-in?provider=github&client_id=stage-web&prompt=login&redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Fauth%2Fcallback')
|
||||
expect(location).toBe('https://auth.airi.build/ui/sign-in?provider=github&client_id=stage-web&prompt=login&redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Fauth%2Fcallback&api_server_url=http%3A%2F%2Flocalhost%3A3000')
|
||||
})
|
||||
|
||||
it('redirects Electron OIDC callback queries to the standalone auth UI relay', async () => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export const SERVER_ADMIN_UI_BASE_PATH = '/admin'
|
||||
export const ADMIN_UI_API_SERVER_URL_QUERY_PARAM = 'api_server_url'
|
||||
export const DEFAULT_ADMIN_UI_URL = 'https://admin.airi.build/admin'
|
||||
export const DEFAULT_ADMIN_UI_URL = 'https://admin.airi.build'
|
||||
export const SERVER_DEV_API_SERVER_URL = 'https://airi-server-dev.up.railway.app'
|
||||
export const SERVER_DEV_ADMIN_UI_URL = 'https://server-dev.airi-server-admin.pages.dev/admin'
|
||||
export const SERVER_DEV_ADMIN_UI_URL = 'https://server-dev.airi-server-admin.pages.dev'
|
||||
|
||||
/**
|
||||
* Builds an absolute URL inside the externally hosted admin UI.
|
||||
@@ -13,7 +13,7 @@ export const SERVER_DEV_ADMIN_UI_URL = 'https://server-dev.airi-server-admin.pag
|
||||
* - Preserving dashboard route paths and query parameters.
|
||||
*
|
||||
* Expects:
|
||||
* - `adminUiUrl` is the public admin UI base, usually ending in `/admin`.
|
||||
* - `adminUiUrl` is the public admin UI base.
|
||||
* - `path` is the route path within the admin UI router.
|
||||
*
|
||||
* Returns:
|
||||
@@ -75,7 +75,7 @@ export function resolveAdminUiUrl(adminUiUrl: string, apiServerUrl: string): str
|
||||
*
|
||||
* Expects:
|
||||
* - `requestUrl` is the incoming server URL.
|
||||
* - `adminUiUrl` points to the standalone admin UI base path.
|
||||
* - `adminUiUrl` points to the standalone admin UI base.
|
||||
*
|
||||
* Returns:
|
||||
* - The external admin UI URL preserving route suffix and query string.
|
||||
|
||||
@@ -4,42 +4,42 @@ import { buildAdminUiRedirectUrl, buildAdminUiUrl, resolveAdminUiUrl } from '../
|
||||
|
||||
describe('admin UI URL helpers', () => {
|
||||
it('builds admin UI URLs under the configured admin base path', () => {
|
||||
expect(buildAdminUiUrl('https://admin.airi.build/admin', '/users', '?query=alice')).toBe(
|
||||
'https://admin.airi.build/admin/users?query=alice',
|
||||
expect(buildAdminUiUrl('https://admin.airi.build', '/users', '?query=alice')).toBe(
|
||||
'https://admin.airi.build/users?query=alice',
|
||||
)
|
||||
})
|
||||
|
||||
it('maps server /admin requests to the standalone admin UI while preserving queries', () => {
|
||||
expect(buildAdminUiRedirectUrl(
|
||||
'https://admin.airi.build/admin/',
|
||||
'https://admin.airi.build/',
|
||||
'https://api.airi.build/admin/voice-packs?provider=openai',
|
||||
)).toBe('https://admin.airi.build/admin/voice-packs?provider=openai')
|
||||
)).toBe('https://admin.airi.build/voice-packs?provider=openai')
|
||||
})
|
||||
|
||||
it('adds the API server origin for standalone admin UI cross-environment redirects', () => {
|
||||
expect(buildAdminUiRedirectUrl(
|
||||
'https://admin-preview.example/admin/',
|
||||
'https://admin-preview.example/',
|
||||
'https://airi-server-dev.up.railway.app/admin/users?api_server_url=https%3A%2F%2Fevil.example',
|
||||
'https://airi-server-dev.up.railway.app/api/admin',
|
||||
)).toBe(
|
||||
'https://admin-preview.example/admin/users?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app',
|
||||
'https://admin-preview.example/users?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app',
|
||||
)
|
||||
})
|
||||
|
||||
it('routes server-dev default admin UI redirects to the matching Pages branch', () => {
|
||||
expect(buildAdminUiRedirectUrl(
|
||||
'https://admin.airi.build/admin',
|
||||
'https://admin.airi.build',
|
||||
'https://airi-server-dev.up.railway.app/admin/users?query=alice',
|
||||
'https://airi-server-dev.up.railway.app',
|
||||
)).toBe(
|
||||
'https://server-dev.airi-server-admin.pages.dev/admin/users?query=alice&api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app',
|
||||
'https://server-dev.airi-server-admin.pages.dev/users?query=alice&api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app',
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps an explicitly configured admin UI URL for server-dev', () => {
|
||||
expect(resolveAdminUiUrl(
|
||||
'https://admin-preview.example/admin',
|
||||
'https://admin-preview.example',
|
||||
'https://airi-server-dev.up.railway.app',
|
||||
)).toBe('https://admin-preview.example/admin')
|
||||
)).toBe('https://admin-preview.example')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# AIRI Admin Dashboard
|
||||
|
||||
Admin dashboard for operating the hosted AIRI server. It is a standalone Vue/Vite app deployed to Cloudflare Pages under `/admin`; the API server redirects its historical `/admin/*` entrypoints to this app.
|
||||
Admin dashboard for operating the hosted AIRI server. It is a standalone Vue/Vite app deployed to Cloudflare Pages; the API server redirects its historical `/admin/*` entrypoints to this app.
|
||||
|
||||
## Use When
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
/ /admin/ 302
|
||||
/admin /index.html 200
|
||||
/admin/* /index.html 200
|
||||
/* /index.html 200
|
||||
|
||||
@@ -20,7 +20,7 @@ import './styles/main.css'
|
||||
import 'uno.css'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory('/admin/'),
|
||||
history: createWebHistory('/'),
|
||||
routes: [
|
||||
{ path: '/', component: OverviewPage },
|
||||
{ path: '/users', component: UsersPage },
|
||||
|
||||
@@ -7,16 +7,16 @@ import { getServerAdminBootstrapContext, resolveStandaloneServerAdminContext } f
|
||||
describe('ui-admin bootstrap context', () => {
|
||||
it('uses the trusted API server origin carried by standalone server redirects', () => {
|
||||
expect(resolveStandaloneServerAdminContext(
|
||||
'https://admin.airi.build/admin/users?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app%2Fapi%2Fadmin',
|
||||
'https://admin.airi.build/users?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app%2Fapi%2Fadmin',
|
||||
)).toEqual({
|
||||
apiServerUrl: 'https://airi-server-dev.up.railway.app',
|
||||
currentUrl: 'https://admin.airi.build/admin/users?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app%2Fapi%2Fadmin',
|
||||
currentUrl: 'https://admin.airi.build/users?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app%2Fapi%2Fadmin',
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores untrusted API server origins from crafted standalone admin URLs', () => {
|
||||
expect(resolveStandaloneServerAdminContext(
|
||||
'https://admin.airi.build/admin/users?api_server_url=https%3A%2F%2Fevil.example',
|
||||
'https://admin.airi.build/users?api_server_url=https%3A%2F%2Fevil.example',
|
||||
)).toBeNull()
|
||||
})
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import VueMacros from 'vue-macros/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
base: '/',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@proj-airi/stage-shared': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-shared', 'src')),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/ /ui/profile 302
|
||||
/auth /ui/ 302
|
||||
/auth/* /ui/:splat 302
|
||||
/ui /index.html 200
|
||||
|
||||
Reference in New Issue
Block a user