fix(auth): classify services by service_type, not name heuristics

Coolify exposes the template slug on `service_type`; the list endpoint
returns only summaries, so the auth list handler now fetches each
service individually to classify it reliably. Users can name auth
services anything (e.g. "my-login") and they still show up as auth
providers.

Made-with: Cursor
This commit is contained in:
2026-04-21 12:37:21 -07:00
parent 62c52747f5
commit de1cd96ec2
2 changed files with 19 additions and 11 deletions

View File

@@ -70,18 +70,23 @@ export async function GET(
try { try {
const all = await listServicesInProject(ws.coolify_project_uuid); const all = await listServicesInProject(ws.coolify_project_uuid);
// Coolify returns all services — we narrow to ones whose name // Coolify's list endpoint only returns summaries (no service_type) so
// contains an auth-provider slug. We also return the full list so // we fetch each service individually to classify it by template slug.
// callers can see non-auth services without a separate endpoint. // This is O(n) in services-per-workspace — acceptable at single-digit
// scales — and avoids name-based heuristics that break on custom names.
const detailed = await Promise.all(all.map(s => getService(s.uuid).catch(() => s)));
return NextResponse.json({ return NextResponse.json({
workspace: { slug: ws.slug, coolifyProjectUuid: ws.coolify_project_uuid }, workspace: { slug: ws.slug, coolifyProjectUuid: ws.coolify_project_uuid },
providers: all providers: detailed
.filter(s => AUTH_PROVIDER_SLUGS.has(deriveTypeFromName(s.name))) .filter(s => {
const t = resolveProviderSlug(s);
return !!t && AUTH_PROVIDER_SLUGS.has(t);
})
.map(s => ({ .map(s => ({
uuid: s.uuid, uuid: s.uuid,
name: s.name, name: s.name,
status: s.status ?? null, status: s.status ?? null,
provider: deriveTypeFromName(s.name), provider: resolveProviderSlug(s),
projectUuid: projectUuidOf(s), projectUuid: projectUuidOf(s),
})), })),
allowedProviders: Object.keys(AUTH_PROVIDERS), allowedProviders: Object.keys(AUTH_PROVIDERS),
@@ -159,14 +164,15 @@ export async function POST(
} }
/** /**
* Coolify names services "{type}-{random-suffix}" when auto-named. We * Authoritative: Coolify stores the template slug on `service_type`.
* recover the provider slug by stripping the trailing `-\w+` if any * Fall back to a name-prefix match so services created before that field
* and matching against our allowlist. Falls back to empty string. * existed still classify correctly.
*/ */
function deriveTypeFromName(name: string): string { function resolveProviderSlug(svc: { name: string; service_type?: string }): string {
if (svc.service_type && AUTH_PROVIDER_SLUGS.has(svc.service_type)) return svc.service_type;
const candidates = Object.values(AUTH_PROVIDERS).sort((a, b) => b.length - a.length); const candidates = Object.values(AUTH_PROVIDERS).sort((a, b) => b.length - a.length);
for (const slug of candidates) { for (const slug of candidates) {
if (name === slug || name.startsWith(`${slug}-`) || name.startsWith(`${slug}_`)) { if (svc.name === slug || svc.name.startsWith(`${slug}-`) || svc.name.startsWith(`${slug}_`)) {
return slug; return slug;
} }
} }

View File

@@ -560,6 +560,8 @@ export interface CoolifyService {
uuid: string; uuid: string;
name: string; name: string;
status?: string; status?: string;
/** Coolify template slug the service was provisioned from, e.g. "pocketbase". */
service_type?: string;
project_uuid?: string; project_uuid?: string;
environment_id?: number; environment_id?: number;
environment?: { id?: number; project_uuid?: string; project?: { uuid?: string } }; environment?: { id?: number; project_uuid?: string; project?: { uuid?: string } };