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 {
const all = await listServicesInProject(ws.coolify_project_uuid);
// Coolify returns all services — we narrow to ones whose name
// contains an auth-provider slug. We also return the full list so
// callers can see non-auth services without a separate endpoint.
// Coolify's list endpoint only returns summaries (no service_type) so
// we fetch each service individually to classify it by template slug.
// 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({
workspace: { slug: ws.slug, coolifyProjectUuid: ws.coolify_project_uuid },
providers: all
.filter(s => AUTH_PROVIDER_SLUGS.has(deriveTypeFromName(s.name)))
providers: detailed
.filter(s => {
const t = resolveProviderSlug(s);
return !!t && AUTH_PROVIDER_SLUGS.has(t);
})
.map(s => ({
uuid: s.uuid,
name: s.name,
status: s.status ?? null,
provider: deriveTypeFromName(s.name),
provider: resolveProviderSlug(s),
projectUuid: projectUuidOf(s),
})),
allowedProviders: Object.keys(AUTH_PROVIDERS),
@@ -159,14 +164,15 @@ export async function POST(
}
/**
* Coolify names services "{type}-{random-suffix}" when auto-named. We
* recover the provider slug by stripping the trailing `-\w+` if any
* and matching against our allowlist. Falls back to empty string.
* Authoritative: Coolify stores the template slug on `service_type`.
* Fall back to a name-prefix match so services created before that field
* 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);
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;
}
}

View File

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