Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4117fc5
feat(web): chat-first redesign — auth screens (Phase 1)
rapidinfosoftllc Jul 15, 2026
95bc99e
feat(web): elevate auth design to Stripe/Apple-grade polish
rapidinfosoftllc Jul 16, 2026
538de9d
feat(web): chat-first app shell + Concierge + Skills/Agents (Phase 2)
rapidinfosoftllc Jul 16, 2026
651300d
feat(web): wire all shell sections to real catalog content (Phase 3)
rapidinfosoftllc Jul 16, 2026
7c7923c
feat(web): Connect-a-register wizard + scope POS to RapidRMS/Verifone…
rapidinfosoftllc Jul 16, 2026
c7af838
feat(web): wire Concierge + connect-store to real APIs + cutover flag…
rapidinfosoftllc Jul 16, 2026
10210e1
feat(web): restructure shell to Claude-design chat+canvas layout (Pha…
rapidinfosoftllc Jul 16, 2026
e17917f
feat(web): Command Home + home<->chat slide, profile/user rail, white…
rapidinfosoftllc Jul 16, 2026
c36c106
feat(web): menu icon opens full nav sidebar as a left drawer (Phase 8)
rapidinfosoftllc Jul 16, 2026
afeb0e9
fix(web): chat/section content overflow — pin slide grid rows so inne…
rapidinfosoftllc Jul 16, 2026
e50940b
feat(web): Canvas/History tabs + recall, consistent docked sidebar, d…
rapidinfosoftllc Jul 16, 2026
1caba07
feat(web): live data layer (no dummy data live) + profile as persiste…
rapidinfosoftllc Jul 16, 2026
2b7f690
feat(web): wire skills/agents/models/apps + home to real AROS endpoints
rapidinfosoftllc Jul 16, 2026
289a22d
feat(web): wire chat replies -> conversation canvas (content blocks)
rapidinfosoftllc Jul 16, 2026
b6d1ca1
feat(web): wire remaining sections (health/billing/usage/team/setting…
rapidinfosoftllc Jul 16, 2026
829d3e9
Merge remote-tracking branch 'origin/main' into feat/chat-first-redesign
rapidinfosoftllc Jul 16, 2026
3779c18
refactor(web): redesign canvas reuses the shared mib-widget renderer …
rapidinfosoftllc Jul 16, 2026
e569132
fix(web): serve redesigned login at auth route
rapidinfosoftllc Jul 16, 2026
5467c70
fix(web): require browser session before login redirect
rapidinfosoftllc Jul 16, 2026
9a4b308
feat(web): make redesigned shell the authenticated default
rapidinfosoftllc Jul 16, 2026
e4880ef
Merge remote-tracking branch 'origin/main' into feat/chat-first-redesign
rapidinfosoftllc Jul 16, 2026
38d568a
docs(changelog): backfill 0.2.0-0.4.0 + record 0.5.0 chat-first redes…
rapidinfosoftllc Jul 16, 2026
7cfd948
docs(changelog): redesign flipped to default by concurrent work (corr…
rapidinfosoftllc Jul 16, 2026
24d3513
feat(web): wire production-ready redesign pages
rapidinfosoftllc Jul 16, 2026
9a857de
fix(web): add password visibility control
rapidinfosoftllc Jul 16, 2026
adfea26
fix(web): separate live shell data from preview mocks
rapidinfosoftllc Jul 16, 2026
c788a16
fix(web): restore guided POS connection journey
rapidinfosoftllc Jul 16, 2026
bb71587
feat(web): align workspace administration with MIB contracts
rapidinfosoftllc Jul 16, 2026
8cb7eb9
fix(connectors): match working MIB RapidRMS auth
rapidinfosoftllc Jul 16, 2026
d8472c2
feat(web): add unified AROS marketplace
rapidinfosoftllc Jul 16, 2026
dc2f909
fix(web): complete connection management feedback
rapidinfosoftllc Jul 16, 2026
c003218
feat: unify marketplace resource activation
rapidinfosoftllc Jul 16, 2026
ac892b6
feat(web): separate marketplace from installed resources
rapidinfosoftllc Jul 16, 2026
e0ed53c
fix(web): repair marketplace links and store management
rapidinfosoftllc Jul 16, 2026
11f5587
fix(web): make Commander onboarding edge-first
rapidinfosoftllc Jul 16, 2026
1c835c5
feat(edge): pair Commander stores during setup
rapidinfosoftllc Jul 16, 2026
edcecba
feat(models): expose router catalog and chat selection
rapidinfosoftllc Jul 16, 2026
05d7a4c
fix(chat): show actual responding model
rapidinfosoftllc Jul 16, 2026
3bd56ce
feat(marketplace): link shared MIB connectors
rapidinfosoftllc Jul 16, 2026
031ce72
fix(server): no-store the HTML shell, keep assets immutable
rapidinfosoftllc Jul 16, 2026
63e4b92
docs(changelog): 0.5.1 — post-flip security hardening + audit results
rapidinfosoftllc Jul 16, 2026
e643fff
feat(auth): enforce centralized identity conformance
rapidinfosoftllc Jul 16, 2026
f661369
feat(auth): wire durable centralized OIDC runtime
rapidinfosoftllc Jul 16, 2026
8266a36
feat(devices): add enrolled computer inventory
rapidinfosoftllc Jul 16, 2026
3984d54
feat(provisioning): reconcile all tenant activations
rapidinfosoftllc Jul 16, 2026
74bd2f9
feat(provisioning): add connector and app manifests
rapidinfosoftllc Jul 16, 2026
f2b68d0
fix(auth): keep established workspaces out of onboarding
rapidinfosoftllc Jul 16, 2026
9a5da97
feat(plugins): unified Plugins surface — tenant-built apps + marketpl…
rapidinfosoftllc Jul 17, 2026
3a01123
feat(plugins): accept trusted MIB service auth on /api/plugins
rapidinfosoftllc Jul 17, 2026
b35ac19
feat(plugins): cross-tenant listing for the operator ops console
rapidinfosoftllc Jul 17, 2026
09fbd10
feat(plugins): provision endpoint — the write path that registers a b…
rapidinfosoftllc Jul 17, 2026
9a018db
fix(plugins): wire the provision endpoint into the server
rapidinfosoftllc Jul 17, 2026
3f9c50c
docs(plugins): operator runbook for enabling Plugins end to end
rapidinfosoftllc Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# Copy to .env and fill values
APP_ENV=development
PORT=3000

# Canonical Shre Identity OIDC relying-party contract. Production values are
# registered in the shared application registry; no client secret is used.
OIDC_ISSUER=https://id.shre.ai
OIDC_CLIENT_ID=aros-web
OIDC_AUDIENCE=aros-api
OIDC_REDIRECT_URI=https://app.aros.live/auth/callback
OIDC_SESSION_TTL_SECONDS=3600
# Production must use supabase. Memory is an explicit single-process local mode.
OIDC_STORE_MODE=memory
# Vault-injected only; required when OIDC_STORE_MODE=supabase. Never commit it.
AROS_OIDC_ENCRYPTION_KEY=
6 changes: 6 additions & 0 deletions .github/workflows/standard-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ jobs:
jq . .claims/claims.json >/dev/null
fi

- name: Auth conformance and secret gate
if: ${{ hashFiles('package.json') != '' }}
run: |
pnpm test:auth-conformance
pnpm security:auth

- name: Run tests
run: |
chmod +x scripts/test.sh || true
Expand Down
52 changes: 48 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,54 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
Versioning: [Semantic Versioning](https://semver.org/).

## [Unreleased]
### In Progress
- Shre brain sync integration
- BYOM model selector
- Licensing module
### Changed
- Expanded the AROS connector Marketplace and linked Gmail, Google Workspace, Microsoft 365, Slack, HubSpot, Mailchimp, SendGrid, and Twilio setup to the canonical MIB configuration surfaces.
- Models now load from the live Shre router catalog, and chat supports explicit Auto, Claude, OpenAI, Gemini, and other connected model selection.
- Chat responses identify the model that actually answered, including transparent local fallback.
- Commander setup now creates the canonical store mapping and a one-time Edge pairing code in the same flow.
- Reworked Verifone Commander onboarding as an Edge-first flow with required store name, optional store number, LAN/Edge guidance, and no misleading cloud-side direct test.
- Added confirmation before removing a store/POS connection.
- Added context-aware Marketplace navigation from Apps, Connectors, Plugins, Skills, and Agents.
- Standardized spacing between page search controls and installed-resource content.
- Store connection management now supports title, details, access mode, provider configuration, and secure credential rotation with automatic re-testing.

### Planned / In Progress
- Converge the redesign canvas onto the shared `mib-widget` content-block contract (once the live fork picks up PR #38)
- Restyle the Login/Signup auth screens to the new theme
- MIB API reuse for resources/POS (blocked on a shre-id tenant↔company token bridge)
- Shre brain sync integration; BYOM model selector; Licensing module

## [0.5.1] — 2026-07-16 — Post-flip security hardening
### Security
- App-shell HTML (and the index.html SPA fallback) now returns `Cache-Control: private, no-store`, so no shared cache/CDN stores the authenticated bootstrap. Fingerprinted `/assets/*` stay `public, immutable`. (`src/server.ts` `sendStaticFile`)
### Verified (post-flip audit, redesign now default)
- **Tenant isolation:** the server never trusts the `x-aros-tenant-id` header — `authenticateRequest()` validates the Bearer token, then requires an active `tenant_members` row for `(user, requested_tenant)`; a mismatch yields zero rows → 401, and the effective tenant is read from the DB membership. Every data query scopes by `auth.tenantId`; workspace routes add an explicit `!== → 403`. Unauthenticated `/api/*` returns 401.
- **No demo-data leak:** demo persona/figures are gated on `useDemo() === !session` and only render on the unauthenticated `/preview/app` route; the served shell embeds no persona. A live session shows real fetches and empty states.

## [0.5.0] — 2026-07-16 — Chat-first redesign (soft launch)
### Added
- Chat-first "Command Home" redesign — warm Stripe/Apple theme (light/dark), gated behind `?redesign=1` (OFF by default; old dashboard unchanged).
- Home ⇄ chat slide; conversation canvas with Canvas/History tabs; 4-step Connect-a-register wizard (RapidRMS + Verifone).
- Left-panel profile (role + workspace nav), whitelabel branding module, responsive (mobile/tablet), consistent docked sidebar.
- Live data wiring: `/api/connectors`, `/api/resources/*`, `/api/dashboard`, `/api/store/summary`, `/api/billing/status`. Demo persona/figures render ONLY when unauthenticated (`/preview/app`) — never in a live session.
### Notes
- Deployed to production on 2026-07-16, reconciled onto the hand-managed VPS live fork (`live/direct-deploy`), not a main-based build. Initially gated behind `?redesign=1`; a concurrent workstream then **flipped it to the default** authenticated experience (legacy opt-out via `?redesign=0` → `aros-shell-legacy`) and added working sign-out + session-establishment fixes (`8e7551a`, `e3d3e7b`, `fe77441`). Per-browser rollback to the legacy UI: `?redesign=0`.

## [0.4.0] — 2026-03-25
### Added
- Health server, web app, security hardening, RBAC, ArosChat redesign.

## [0.3.1] — 2026-03-18
### Added
- Plugin & connector developer guide, DATA_PLUGIN_GUIDE, first-party app catalog.

## [0.3.0] — 2026-03-18
### Added
- RapidRMS connector, AWS RDS connector, Conexxus local store, third-party plugin docs, marketplace database nodes.

## [0.2.0] — 2026-03-18
### Added
- Public release: BSL license, BYOM enforcement, AI Models settings UI, rapidrms-ops submodule.

## [0.1.0] — 2026-03-18
### Added
Expand Down
87 changes: 87 additions & 0 deletions appfactory/sql-executor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// ── App Factory SQL executor + role-secret vault (Phase 2) ──────────────────
// Privileged Postgres executor that runs the provisioning DDL (CREATE SCHEMA /
// ROLE) for a generated app, plus the file-vault write for the generated role
// password. Both are config-gated: when no provisioning DB is configured the
// executor is null and the endpoint degrades to 501 — the same "operator must
// wire this" posture as the staged-apply tenant_apps migration.
//
// Connection + secret file conventions mirror connectors/rapidrms/
// analytics-connector.ts (pg Pool from ~/.shre/vault/*.json).

import { createRequire } from 'node:module';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
import { homedir } from 'node:os';
import type { SqlExecutor } from './types.js';

// pg ships no bundled types in this workspace (same as connectors/rapidrms/
// analytics-connector.ts); load it via require so tsc needs no @types/pg.
const nodeRequire = createRequire(import.meta.url);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const pg: any = nodeRequire('pg');

// eslint-disable-next-line @typescript-eslint/no-explicit-any
let sharedPool: any = null;

interface DbVaultFile {
connectionString?: string;
host?: string;
port?: number;
user?: string;
password?: string;
database?: string;
}

/** Resolve the privileged provisioning connection string, or null if unset.
* Order: APP_FACTORY_DATABASE_URL, then ~/.shre/vault/app-factory-db.json. */
function loadConnectionString(): string | null {
if (process.env.APP_FACTORY_DATABASE_URL?.trim()) return process.env.APP_FACTORY_DATABASE_URL.trim();
const vaultFile = join(homedir(), '.shre', 'vault', 'app-factory-db.json');
try {
if (existsSync(vaultFile)) {
const creds = JSON.parse(readFileSync(vaultFile, 'utf8')) as DbVaultFile;
if (creds.connectionString) return creds.connectionString;
if (creds.host && creds.user) {
const auth = `${encodeURIComponent(creds.user)}:${encodeURIComponent(creds.password ?? '')}`;
return `postgresql://${auth}@${creds.host}:${creds.port ?? 5432}/${creds.database ?? 'postgres'}`;
}
}
} catch { /* fall through to null */ }
return null;
}

/**
* A privileged SqlExecutor for App Factory provisioning DDL, or null when no
* provisioning DB is configured (the caller then returns 501). Uses the simple
* query protocol so multi-statement DDL (incl. DO $$ blocks) runs in one call.
*/
export function createProvisioningSqlExecutor(): SqlExecutor | null {
const conn = loadConnectionString();
if (!conn) return null;
if (!sharedPool) {
sharedPool = new pg.Pool({ connectionString: conn, max: 3, idleTimeoutMillis: 30_000, connectionTimeoutMillis: 10_000 });
sharedPool.on('error', (err: Error) => console.error('[appfactory.sql] pool error:', err.message));
}
const pool = sharedPool;
return {
async exec(sql: string): Promise<void> {
await pool.query(sql);
},
};
}

/**
* Persist a generated app's role credential to the file-vault
* (~/.shre/vault/app-<subdomain>.json, 0600). The password is returned once by
* provisionApp and MUST NOT travel back over the API — this is where it lands.
*/
export function storeRoleSecret(
subdomain: string,
payload: { role: string; password: string; schema: string; tenantId: string },
): string {
const dir = join(homedir(), '.shre', 'vault');
mkdirSync(dir, { recursive: true });
const file = join(dir, `app-${subdomain}.json`);
writeFileSync(file, JSON.stringify(payload, null, 2), { mode: 0o600 });
return file;
}
18 changes: 14 additions & 4 deletions apps/web/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { AdministrationPage } from '../pages/settings/AdministrationPage';
import AIModelsSettings from '../pages/settings/AIModels';
import { CapabilityCatalog } from '../pages/settings/CapabilityCatalog';
import { ConnectionHealth } from '../pages/settings/ConnectionHealth';
import { DevicesPage } from '../redesign/pages/admin';
import { centralIdentityOnly } from '../lib/supabase';
import { AppShell } from '../redesign/AppShell';

Expand All @@ -42,10 +43,18 @@ function isOnboardingComplete(tenant: Tenant | null): boolean {
}

function AppContent() {
const { user, session, tenant, loading } = useSupabaseAuth();
const { user, session, tenant, loading, membershipError } = useSupabaseAuth();
const path = window.location.pathname;
const isAdmin = user?.app_metadata?.role === 'admin' || user?.app_metadata?.role === 'superadmin';
const onboarded = isOnboardingComplete(tenant);
// A failed membership lookup is not evidence of a new workspace. Route an
// authenticated session to the dashboard so ProtectedRoute can show/retry
// the shared-backend lookup instead of incorrectly starting onboarding.
const authenticatedLanding = membershipError
? '/dashboard'
: tenant
? (onboarded ? '/dashboard' : '/start')
: '/start';

// Chat-first redesign preview — self-contained, no auth, for review only.
if (path.startsWith('/preview/app')) {
Expand All @@ -58,7 +67,7 @@ function AppContent() {
if (redesignParam === '1') { try { localStorage.removeItem('aros-shell-legacy'); } catch { /* ignore */ } }
else if (redesignParam === '0') { try { localStorage.setItem('aros-shell-legacy', '1'); } catch { /* ignore */ } }

if (centralIdentityOnly && path !== '/login' && path !== '/signup') {
if (centralIdentityOnly && !loading && !session && path !== '/login' && path !== '/signup') {
const authorize = path === '/oauth/authorize' ? `${path}${window.location.search}` : null;
window.location.replace(authorize ? `/login?return_to=${encodeURIComponent(authorize)}` : '/login');
return null;
Expand All @@ -69,15 +78,15 @@ function AppContent() {
const isHostedResume = new URLSearchParams(window.location.search).has('return_to');
if (session && !loading && !isHostedResume) {
// New users land in the value-first demo chat (/start), not the wizard.
window.location.href = onboarded ? '/dashboard' : '/start';
window.location.href = authenticatedLanding;
return null;
}
return <><Login /><ChatWidget /></>;
}

if (path === '/signup') {
if (session && !loading) {
window.location.href = onboarded ? '/dashboard' : '/start';
window.location.href = authenticatedLanding;
return null;
}
return <><Signup /><ChatWidget /></>;
Expand Down Expand Up @@ -204,6 +213,7 @@ function AuthenticatedRoutes({ path, isAdmin, onboarded }: { path: string; isAdm
: path.startsWith('/agents') ? <CapabilityCatalog kind="agents" />
: path.startsWith('/skills') ? <CapabilityCatalog kind="skills" />
: path.startsWith('/models') ? <AIModelsSettings />
: path.startsWith('/computers') ? <DevicesPage />
: path.startsWith('/connection-health') ? <ConnectionHealth />
: path.startsWith('/settings') ? <AdministrationPage section="settings" />
: path.startsWith('/profile') ? <AdministrationPage section="profile" />
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/app/aros-shell.css
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,16 @@
box-shadow: var(--shadow-btn); transition: background 0.15s, box-shadow 0.15s;
}
.rsx-panel__cta:hover { background: var(--accent-2); box-shadow: var(--shadow-btn-hover); }
.rsx-marketplace-layout { display: flex; flex-direction: column; gap: 22px; }
.rsx-marketplace-layout .rsx-panel__head,
.rsx-marketplace-layout .rsx-form { margin: 0; }
.rsx-marketplace-layout .rsx2-tabs { align-self: flex-start; }
.rsx-section-actions { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.rsx-marketplace-layout { display: flex; flex-direction: column; gap: 22px; }
.rsx-marketplace-layout .rsx-panel__head,
.rsx-marketplace-layout .rsx-form { margin: 0; }
.rsx-marketplace-layout .rsx2-tabs { align-self: flex-start; }
.rsx-section-actions { display: flex; align-items: center; gap: 10px; margin-left: auto; }

.rsx-stats { display: flex; gap: 12px; margin-bottom: 20px; }
.rsx-stat {
Expand Down Expand Up @@ -254,8 +264,24 @@
cursor: pointer; flex-shrink: 0; transition: background 0.13s, border-color 0.13s;
}
.rsx-row__btn:hover { background: var(--surface-2); border-color: var(--accent); }
.rsx-row__btn--primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.rsx-row__btn--primary:hover { background: var(--accent-2); border-color: var(--accent-2); }
.rsx-row__btn:disabled { opacity: 0.6; cursor: default; }

/* Group heading inside a panel (e.g. Plugins: Built vs Installed) */
.rsx-group__label {
font-size: 12px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
color: var(--ink-3); margin: 22px 0 10px;
}
.rsx-group__label:first-of-type { margin-top: 4px; }

.rsx-form { display: flex; flex-direction: column; gap: 14px; max-width: 460px; }
.rsx-panel > .rsx-form { margin-bottom: 20px; }
.rsx-search-block { display: block; max-width: 460px; margin-bottom: 20px; }
.rsx-edge-code { margin: 18px 0; padding: 18px; border: 1px solid var(--line-strong); border-radius: 12px; background: var(--surface-2); text-align: center; font: 700 24px/1.2 'JetBrains Mono', monospace; letter-spacing: .12em; }
.rsx-edge-steps { margin: 0 0 18px; padding-left: 22px; color: var(--ink-2); font-size: 13.5px; line-height: 1.7; }
.aros-model-picker { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; color: var(--ink-3); font-size: 11.5px; font-weight: 600; }
.aros-model-picker select { min-width: 220px; max-width: 100%; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); color: var(--ink); padding: 7px 9px; font: 12px inherit; }
.rsx-form__field { display: flex; flex-direction: column; gap: 6px; }
.rsx-form__label { font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.rsx-form__input {
Expand Down Expand Up @@ -299,6 +325,10 @@
.rsx-modal__body { padding: 18px 22px; overflow-y: auto; }
.rsx-modal__h { font-size: 17px; font-weight: 600; margin: 0 0 6px; }
.rsx-modal__p { font-size: 13.5px; color: var(--ink-2); line-height: 1.55; margin: 0 0 16px; }
.rsx-secret { position: relative; display: block; }
.rsx-secret .rsx-form__input, .rsx-secret > input { padding-right: 58px; }
.rsx-secret__toggle { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); border: 0; background: transparent; color: var(--ink-2); font: 600 12px inherit; cursor: pointer; padding: 6px; }
.rsx-secret__toggle:hover, .rsx-secret__toggle:focus-visible { color: var(--ink); }

.rsx-prov { display: flex; flex-direction: column; gap: 10px; }
.rsx-prov__card {
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export function Sidebar() {
<Nav href="/agents" label="Agents" icon="◎" path={path} onClick={close} />
<Nav href="/skills" label="Skills" icon="✣" path={path} onClick={close} />
<Nav href="/models" label="Models" icon="✦" path={path} onClick={close} />
<Nav href="/computers" label="Computers" icon="▣" path={path} onClick={close} />
<Nav href="/connection-health" label="Connection health" icon="◉" path={path} onClick={close} />
<div className="aros-nav-label">Administration</div>
<Nav href="/settings" label="Settings" icon="⚙" path={path} onClick={close} />
Expand Down
14 changes: 11 additions & 3 deletions apps/web/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {

useEffect(() => {
if (centralIdentityOnly) {
setLoading(false);
fetch(`${API_BASE}/auth/session`, { credentials: 'include' }).then(async response => {
if (!response.ok) return;
const identity = await response.json() as { subject: string; workspaceId: string; role: TenantMembership['role'] };
const centralUser = { id: identity.subject, aud: 'authenticated', role: 'authenticated', email: '', user_metadata: {}, app_metadata: { role: identity.role } } as unknown as User;
const centralTenant = { id: identity.workspaceId, name: 'Your workspace', slug: identity.workspaceId, owner_id: '', plan: '', onboarding_completed: true, created_at: '' };
setUser(centralUser); setSession({ user: centralUser, access_token: '', refresh_token: '', expires_in: 3600, token_type: 'bearer' } as Session); setTenant(centralTenant);
setMemberships([{ tenant_id: identity.workspaceId, role: identity.role, is_default: true, status: 'active', tenant: centralTenant }]);
}).catch(() => undefined).finally(() => setLoading(false));
return;
}
supabase.auth.getSession()
Expand Down Expand Up @@ -203,7 +210,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, [memberships]);

const signIn = useCallback(async (email: string, password: string) => {
if (centralIdentityOnly) return { error: 'Use the central AROS sign-in flow.' };
if (centralIdentityOnly) { window.location.assign(`/auth/oidc/start?returnTo=${encodeURIComponent('/dashboard')}`); return { error: null }; }
try {
const res = await fetch(`${API_BASE}/api/login`, {
method: 'POST',
Expand Down Expand Up @@ -243,7 +250,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, []);

const signOut = useCallback(async () => {
await supabase.auth.signOut();
if (centralIdentityOnly) await fetch(`${API_BASE}/auth/logout`, { method: 'POST', credentials: 'include' });
else await supabase.auth.signOut();
setUser(null);
setSession(null);
setMemberships([]);
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/pages/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useAuth } from '../contexts/AuthContext';
import { safeReturnTo } from '../app-registry';
import { hostedAuth, safeIssuerReturnTo, type HostedChallenge, type HostedWorkspace } from '../lib/hosted-auth';
import { useArosTheme } from '../lib/useArosTheme';
import { centralIdentityOnly } from '../lib/supabase';

const AUTH_BASE = (window as any).__SHRE_AUTH_URL__
|| (window.location.hostname === 'localhost' ? 'http://localhost:5455' : '');
Expand Down Expand Up @@ -94,7 +95,7 @@ export function Login() {
<div className="aros-auth__notice">Account created. Sign in to get started.</div>
)}

{workspaces.length > 0 && !challenge ? (
{centralIdentityOnly ? <button type="button" className="aros-auth__btn" onClick={() => window.location.assign(`/auth/oidc/start?returnTo=${encodeURIComponent(returnTo)}`)}>Continue with Shre ID</button> : workspaces.length > 0 && !challenge ? (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<div className="aros-auth__label" style={{ marginBottom: 0 }}>Choose a workspace</div>
{workspaces.map(workspace => (
Expand Down
Loading