mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
feat: swap Redis for Dragonfly, fix remote installs, self-hosted email-only auth
- Replace redis:7-alpine with Dragonfly (Redis-protocol compatible; go-redis client unchanged) in root, infra, and backend-dev compose files. Bounded defaults --proactor_threads/--maxmemory so it boots on small VPSs. - Frontend now defaults to same-origin API calls; nginx proxies /api/* to the backend (WebSocket-aware, docker DNS resolver for upstream re-resolution). Removes the baked localhost:8082 that broke installs reached by IP/domain. - /auth/bootstrap returns configured OAuth providers; the UI hides GitHub/Google buttons unless real credentials exist. Self-hosted stays email/password only - OAuth reserved for a future managed offering. - Sidecar ignores PLACEHOLDER_* OAuth credentials and trusts same-host origins dynamically (Origin host == request Host) so remote installs pass origin checks without config; cross-origin requests still rejected. - install.sh writes ENVIRONMENT=production; COOKIE_SECURE=false in production is now a startup warning instead of fatal (plain-HTTP installs). - Bootstrap failure surfaces an error notice instead of silently rendering the sign-in page. Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
9ba2bd06c5
commit
060038cb6f
21 files changed
+207
-83
No files matched your search
@@ -114,6 +114,7 @@ export function SignInPage() {
|
||||
queryKey: ['auth-bootstrap'],
|
||||
queryFn: getAuthBootstrap,
|
||||
});
|
||||
const providers = bootstrapQuery.data?.providers ?? [];
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -168,10 +169,14 @@ export function SignInPage() {
|
||||
|
||||
return (
|
||||
<AuthLayout>
|
||||
<AuthCard title="Sign In" subtitle="Use email/password, GitHub, or Google.">
|
||||
<AuthCard
|
||||
title="Sign In"
|
||||
subtitle={providers.length > 0 ? 'Use email/password or a connected provider.' : 'Use your email and password.'}
|
||||
>
|
||||
{error ? <AuthErrorNotice message={error} /> : null}
|
||||
{info ? <AuthInfoNotice message={info} /> : null}
|
||||
{bootstrapQuery.isLoading ? <AuthInfoNotice message="Checking platform access mode..." /> : null}
|
||||
{bootstrapQuery.isError ? <AuthErrorNotice message="Cannot reach the Containr API - check that the backend container is running." /> : null}
|
||||
|
||||
<form className="space-y-3" onSubmit={submitEmailPassword}>
|
||||
<label className="block text-xs font-medium uppercase tracking-wider text-[var(--text-muted)]">
|
||||
@@ -211,30 +216,38 @@ export function SignInPage() {
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="my-5 flex items-center gap-3">
|
||||
<div className="h-px flex-1 bg-[var(--border-subtle)]" />
|
||||
<span className="text-[11px] uppercase tracking-wider text-[var(--text-muted)]">or continue with</span>
|
||||
<div className="h-px flex-1 bg-[var(--border-subtle)]" />
|
||||
</div>
|
||||
{providers.length > 0 ? (
|
||||
<>
|
||||
<div className="my-5 flex items-center gap-3">
|
||||
<div className="h-px flex-1 bg-[var(--border-subtle)]" />
|
||||
<span className="text-[11px] uppercase tracking-wider text-[var(--text-muted)]">or continue with</span>
|
||||
<div className="h-px flex-1 bg-[var(--border-subtle)]" />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void signInWithGitHubProvider()}
|
||||
className="inline-flex h-10 items-center justify-center gap-2 rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--surface-muted)] text-sm font-medium text-[var(--text-primary)] transition-colors hover:border-[var(--border-default)]"
|
||||
>
|
||||
<Github size={15} />
|
||||
GitHub
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void signInWithGoogleProvider()}
|
||||
className="inline-flex h-10 items-center justify-center gap-2 rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--surface-muted)] text-sm font-medium text-[var(--text-primary)] transition-colors hover:border-[var(--border-default)]"
|
||||
>
|
||||
<GoogleMark />
|
||||
Google
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{providers.includes('github') ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void signInWithGitHubProvider()}
|
||||
className="inline-flex h-10 items-center justify-center gap-2 rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--surface-muted)] text-sm font-medium text-[var(--text-primary)] transition-colors hover:border-[var(--border-default)]"
|
||||
>
|
||||
<Github size={15} />
|
||||
GitHub
|
||||
</button>
|
||||
) : null}
|
||||
{providers.includes('google') ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void signInWithGoogleProvider()}
|
||||
className="inline-flex h-10 items-center justify-center gap-2 rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--surface-muted)] text-sm font-medium text-[var(--text-primary)] transition-colors hover:border-[var(--border-default)]"
|
||||
>
|
||||
<GoogleMark />
|
||||
Google
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<div className="mt-5 flex items-center justify-between text-xs text-[var(--text-secondary)]">
|
||||
<span>Need access?</span>
|
||||
@@ -292,6 +305,7 @@ export function SignUpPage() {
|
||||
<AuthCard title="Create Account" subtitle="Create first platform owner account. Registration closes after bootstrap.">
|
||||
{error ? <AuthErrorNotice message={error} /> : null}
|
||||
{bootstrapQuery.isLoading ? <AuthInfoNotice message="Checking platform bootstrap state..." /> : null}
|
||||
{bootstrapQuery.isError ? <AuthErrorNotice message="Cannot reach the Containr API - check that the backend container is running." /> : null}
|
||||
|
||||
<form className="space-y-3" onSubmit={submitSignUp}>
|
||||
<label className="block text-xs font-medium uppercase tracking-wider text-[var(--text-muted)]">
|
||||
|
||||
@@ -348,7 +348,8 @@ type ProjectsResponse200 = paths['/projects']['get']['responses'][200]['content'
|
||||
|
||||
type JsonLike = Record<string, unknown>;
|
||||
|
||||
const rawBase = (import.meta.env.VITE_API_URL as string | undefined) ?? 'http://localhost:8082';
|
||||
const configuredBase = ((import.meta.env.VITE_API_URL as string | undefined) ?? '').trim();
|
||||
const rawBase = configuredBase || window.location.origin;
|
||||
const normalizedBase = rawBase.replace(/\/$/, '');
|
||||
const API_ROOT = normalizedBase.replace(/\/api\/v1$/, '').replace(/\/api$/, '');
|
||||
const API_BASE = /\/api\/v1$/.test(normalizedBase) ? normalizedBase : `${normalizedBase}/api/v1`;
|
||||
|
||||
@@ -20,6 +20,7 @@ export type AuthBootstrap = {
|
||||
hasUsers: boolean;
|
||||
userCount: number;
|
||||
mode: 'register' | 'login';
|
||||
providers: string[];
|
||||
};
|
||||
|
||||
export class AuthError extends Error {
|
||||
@@ -32,15 +33,16 @@ export class AuthError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
const rawAuthBase = (import.meta.env.VITE_AUTH_URL as string | undefined) ?? 'http://localhost:8082/api/auth';
|
||||
const AUTH_BASE = rawAuthBase.replace(/\/$/, '');
|
||||
const configuredAuthBase = ((import.meta.env.VITE_AUTH_URL as string | undefined) ?? '').trim();
|
||||
const AUTH_BASE = (configuredAuthBase || `${window.location.origin}/api/auth`).replace(/\/$/, '');
|
||||
|
||||
export function getAuthBaseUrl(): string {
|
||||
return AUTH_BASE;
|
||||
}
|
||||
|
||||
export async function getAuthBootstrap(): Promise<AuthBootstrap> {
|
||||
const apiBase = ((import.meta.env.VITE_API_URL as string | undefined) ?? 'http://localhost:8082').replace(/\/$/, '');
|
||||
const configuredApiBase = ((import.meta.env.VITE_API_URL as string | undefined) ?? '').trim();
|
||||
const apiBase = (configuredApiBase || window.location.origin).replace(/\/$/, '');
|
||||
const normalizedApiBase = /\/api\/v1$/.test(apiBase) ? apiBase : `${apiBase}/api/v1`;
|
||||
const response = await fetch(`${normalizedApiBase}/auth/bootstrap`, {
|
||||
method: 'GET',
|
||||
@@ -56,10 +58,15 @@ export async function getAuthBootstrap(): Promise<AuthBootstrap> {
|
||||
throw new AuthError(message, response.status);
|
||||
}
|
||||
|
||||
const providers = Array.isArray(payload?.providers)
|
||||
? payload.providers.filter((provider): provider is string => typeof provider === 'string')
|
||||
: [];
|
||||
|
||||
return {
|
||||
hasUsers: Boolean(payload?.has_users),
|
||||
userCount: typeof payload?.user_count === 'number' ? payload.user_count : 0,
|
||||
mode: payload?.mode === 'register' ? 'register' : 'login',
|
||||
providers,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user