iec-builder 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +111 -0
- package/.iec.yaml +5 -0
- package/CLAUDE.md +174 -0
- package/Dockerfile +34 -0
- package/README.md +84 -0
- package/catalog-info.yaml +11 -0
- package/dist/config/env.d.ts +219 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +89 -0
- package/dist/config/env.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +43 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +217 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/org-access.d.ts +28 -0
- package/dist/middleware/org-access.d.ts.map +1 -0
- package/dist/middleware/org-access.js +102 -0
- package/dist/middleware/org-access.js.map +1 -0
- package/dist/models/types.d.ts +254 -0
- package/dist/models/types.d.ts.map +1 -0
- package/dist/models/types.js +2 -0
- package/dist/models/types.js.map +1 -0
- package/dist/routes/ai.d.ts +2 -0
- package/dist/routes/ai.d.ts.map +1 -0
- package/dist/routes/ai.js +77 -0
- package/dist/routes/ai.js.map +1 -0
- package/dist/routes/audit.d.ts +2 -0
- package/dist/routes/audit.d.ts.map +1 -0
- package/dist/routes/audit.js +102 -0
- package/dist/routes/audit.js.map +1 -0
- package/dist/routes/builds.d.ts +2 -0
- package/dist/routes/builds.d.ts.map +1 -0
- package/dist/routes/builds.js +262 -0
- package/dist/routes/builds.js.map +1 -0
- package/dist/routes/cluster.d.ts +2 -0
- package/dist/routes/cluster.d.ts.map +1 -0
- package/dist/routes/cluster.js +181 -0
- package/dist/routes/cluster.js.map +1 -0
- package/dist/routes/config.d.ts +2 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +291 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/databases.d.ts +2 -0
- package/dist/routes/databases.d.ts.map +1 -0
- package/dist/routes/databases.js +161 -0
- package/dist/routes/databases.js.map +1 -0
- package/dist/routes/db-whitelist.d.ts +2 -0
- package/dist/routes/db-whitelist.d.ts.map +1 -0
- package/dist/routes/db-whitelist.js +148 -0
- package/dist/routes/db-whitelist.js.map +1 -0
- package/dist/routes/domains.d.ts +2 -0
- package/dist/routes/domains.d.ts.map +1 -0
- package/dist/routes/domains.js +449 -0
- package/dist/routes/domains.js.map +1 -0
- package/dist/routes/oauth.d.ts +2 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +180 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/observability.d.ts +2 -0
- package/dist/routes/observability.d.ts.map +1 -0
- package/dist/routes/observability.js +167 -0
- package/dist/routes/observability.js.map +1 -0
- package/dist/routes/orgs.d.ts +2 -0
- package/dist/routes/orgs.d.ts.map +1 -0
- package/dist/routes/orgs.js +270 -0
- package/dist/routes/orgs.js.map +1 -0
- package/dist/routes/platform.d.ts +2 -0
- package/dist/routes/platform.d.ts.map +1 -0
- package/dist/routes/platform.js +107 -0
- package/dist/routes/platform.js.map +1 -0
- package/dist/routes/push.d.ts +2 -0
- package/dist/routes/push.d.ts.map +1 -0
- package/dist/routes/push.js +233 -0
- package/dist/routes/push.js.map +1 -0
- package/dist/routes/rotation.d.ts +3 -0
- package/dist/routes/rotation.d.ts.map +1 -0
- package/dist/routes/rotation.js +154 -0
- package/dist/routes/rotation.js.map +1 -0
- package/dist/routes/services.d.ts +2 -0
- package/dist/routes/services.d.ts.map +1 -0
- package/dist/routes/services.js +246 -0
- package/dist/routes/services.js.map +1 -0
- package/dist/routes/storage.d.ts +2 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +118 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/users.d.ts +2 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +183 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/routes/versions.d.ts +2 -0
- package/dist/routes/versions.d.ts.map +1 -0
- package/dist/routes/versions.js +195 -0
- package/dist/routes/versions.js.map +1 -0
- package/dist/routes/webhooks.d.ts +2 -0
- package/dist/routes/webhooks.d.ts.map +1 -0
- package/dist/routes/webhooks.js +334 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.d.ts +2 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.d.ts.map +1 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.js +482 -0
- package/dist/services/__tests__/deploy-pipeline.integration.test.js.map +1 -0
- package/dist/services/bio-client.d.ts +68 -0
- package/dist/services/bio-client.d.ts.map +1 -0
- package/dist/services/bio-client.js +110 -0
- package/dist/services/bio-client.js.map +1 -0
- package/dist/services/build-queue.d.ts +7 -0
- package/dist/services/build-queue.d.ts.map +1 -0
- package/dist/services/build-queue.js +114 -0
- package/dist/services/build-queue.js.map +1 -0
- package/dist/services/builder.d.ts +7 -0
- package/dist/services/builder.d.ts.map +1 -0
- package/dist/services/builder.js +1384 -0
- package/dist/services/builder.js.map +1 -0
- package/dist/services/catalog.d.ts +177 -0
- package/dist/services/catalog.d.ts.map +1 -0
- package/dist/services/catalog.js +805 -0
- package/dist/services/catalog.js.map +1 -0
- package/dist/services/catalog.test.d.ts +2 -0
- package/dist/services/catalog.test.d.ts.map +1 -0
- package/dist/services/catalog.test.js +467 -0
- package/dist/services/catalog.test.js.map +1 -0
- package/dist/services/cloudflare.d.ts +43 -0
- package/dist/services/cloudflare.d.ts.map +1 -0
- package/dist/services/cloudflare.js +182 -0
- package/dist/services/cloudflare.js.map +1 -0
- package/dist/services/config-validator.d.ts +28 -0
- package/dist/services/config-validator.d.ts.map +1 -0
- package/dist/services/config-validator.js +68 -0
- package/dist/services/config-validator.js.map +1 -0
- package/dist/services/config-validator.test.d.ts +2 -0
- package/dist/services/config-validator.test.d.ts.map +1 -0
- package/dist/services/config-validator.test.js +151 -0
- package/dist/services/config-validator.test.js.map +1 -0
- package/dist/services/crypto.d.ts +19 -0
- package/dist/services/crypto.d.ts.map +1 -0
- package/dist/services/crypto.js +63 -0
- package/dist/services/crypto.js.map +1 -0
- package/dist/services/database.d.ts +26 -0
- package/dist/services/database.d.ts.map +1 -0
- package/dist/services/database.js +100 -0
- package/dist/services/database.js.map +1 -0
- package/dist/services/db-credential-manager.d.ts +73 -0
- package/dist/services/db-credential-manager.d.ts.map +1 -0
- package/dist/services/db-credential-manager.js +342 -0
- package/dist/services/db-credential-manager.js.map +1 -0
- package/dist/services/db-provisioner.d.ts +57 -0
- package/dist/services/db-provisioner.d.ts.map +1 -0
- package/dist/services/db-provisioner.js +400 -0
- package/dist/services/db-provisioner.js.map +1 -0
- package/dist/services/db-provisioner.test.d.ts +2 -0
- package/dist/services/db-provisioner.test.d.ts.map +1 -0
- package/dist/services/db-provisioner.test.js +141 -0
- package/dist/services/db-provisioner.test.js.map +1 -0
- package/dist/services/db-whitelist.d.ts +58 -0
- package/dist/services/db-whitelist.d.ts.map +1 -0
- package/dist/services/db-whitelist.js +379 -0
- package/dist/services/db-whitelist.js.map +1 -0
- package/dist/services/dependency-resolver.d.ts +58 -0
- package/dist/services/dependency-resolver.d.ts.map +1 -0
- package/dist/services/dependency-resolver.js +180 -0
- package/dist/services/dependency-resolver.js.map +1 -0
- package/dist/services/dependency-resolver.test.d.ts +2 -0
- package/dist/services/dependency-resolver.test.d.ts.map +1 -0
- package/dist/services/dependency-resolver.test.js +195 -0
- package/dist/services/dependency-resolver.test.js.map +1 -0
- package/dist/services/deploy-gate.d.ts +19 -0
- package/dist/services/deploy-gate.d.ts.map +1 -0
- package/dist/services/deploy-gate.js +56 -0
- package/dist/services/deploy-gate.js.map +1 -0
- package/dist/services/deploy-gate.test.d.ts +2 -0
- package/dist/services/deploy-gate.test.d.ts.map +1 -0
- package/dist/services/deploy-gate.test.js +199 -0
- package/dist/services/deploy-gate.test.js.map +1 -0
- package/dist/services/dockerfile-generator.d.ts +31 -0
- package/dist/services/dockerfile-generator.d.ts.map +1 -0
- package/dist/services/dockerfile-generator.js +544 -0
- package/dist/services/dockerfile-generator.js.map +1 -0
- package/dist/services/dockerfile-generator.test.d.ts +2 -0
- package/dist/services/dockerfile-generator.test.d.ts.map +1 -0
- package/dist/services/dockerfile-generator.test.js +144 -0
- package/dist/services/dockerfile-generator.test.js.map +1 -0
- package/dist/services/forgejo.d.ts +58 -0
- package/dist/services/forgejo.d.ts.map +1 -0
- package/dist/services/forgejo.js +131 -0
- package/dist/services/forgejo.js.map +1 -0
- package/dist/services/koko.d.ts +153 -0
- package/dist/services/koko.d.ts.map +1 -0
- package/dist/services/koko.js +260 -0
- package/dist/services/koko.js.map +1 -0
- package/dist/services/kubernetes.d.ts +16 -0
- package/dist/services/kubernetes.d.ts.map +1 -0
- package/dist/services/kubernetes.js +102 -0
- package/dist/services/kubernetes.js.map +1 -0
- package/dist/services/oauth-provisioner.d.ts +30 -0
- package/dist/services/oauth-provisioner.d.ts.map +1 -0
- package/dist/services/oauth-provisioner.js +182 -0
- package/dist/services/oauth-provisioner.js.map +1 -0
- package/dist/services/oauth-provisioner.test.d.ts +2 -0
- package/dist/services/oauth-provisioner.test.d.ts.map +1 -0
- package/dist/services/oauth-provisioner.test.js +349 -0
- package/dist/services/oauth-provisioner.test.js.map +1 -0
- package/dist/services/pod-diagnostics.d.ts +11 -0
- package/dist/services/pod-diagnostics.d.ts.map +1 -0
- package/dist/services/pod-diagnostics.js +201 -0
- package/dist/services/pod-diagnostics.js.map +1 -0
- package/dist/services/rotation-scheduler.d.ts +2 -0
- package/dist/services/rotation-scheduler.d.ts.map +1 -0
- package/dist/services/rotation-scheduler.js +215 -0
- package/dist/services/rotation-scheduler.js.map +1 -0
- package/dist/services/storage-credential-manager.d.ts +43 -0
- package/dist/services/storage-credential-manager.d.ts.map +1 -0
- package/dist/services/storage-credential-manager.js +159 -0
- package/dist/services/storage-credential-manager.js.map +1 -0
- package/dist/services/storage-provisioner.d.ts +32 -0
- package/dist/services/storage-provisioner.d.ts.map +1 -0
- package/dist/services/storage-provisioner.js +136 -0
- package/dist/services/storage-provisioner.js.map +1 -0
- package/dist/services/storage.d.ts +65 -0
- package/dist/services/storage.d.ts.map +1 -0
- package/dist/services/storage.js +204 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/troubleshooter.d.ts +22 -0
- package/dist/services/troubleshooter.d.ts.map +1 -0
- package/dist/services/troubleshooter.js +168 -0
- package/dist/services/troubleshooter.js.map +1 -0
- package/dist/services/vault-client.d.ts +114 -0
- package/dist/services/vault-client.d.ts.map +1 -0
- package/dist/services/vault-client.js +411 -0
- package/dist/services/vault-client.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +6 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/response.d.ts +13 -0
- package/dist/utils/response.d.ts.map +1 -0
- package/dist/utils/response.js +12 -0
- package/dist/utils/response.js.map +1 -0
- package/docs/registry-migration.md +301 -0
- package/docs/registry-quickstart.md +169 -0
- package/ecosystem.config.cjs +14 -0
- package/findings.md +168 -0
- package/helm/default-service/Chart.yaml +6 -0
- package/helm/default-service/templates/deployment.yaml +97 -0
- package/helm/default-service/templates/ingress.yaml +43 -0
- package/helm/default-service/templates/service.yaml +17 -0
- package/helm/default-service/values.yaml +82 -0
- package/helm/services/iec-builder/Chart.yaml +6 -0
- package/helm/services/iec-builder/templates/_helpers.tpl +61 -0
- package/helm/services/iec-builder/templates/deployment.yaml +73 -0
- package/helm/services/iec-builder/templates/service.yaml +15 -0
- package/helm/services/iec-builder/templates/serviceaccount.yaml +12 -0
- package/helm/services/iec-builder/values.yaml +56 -0
- package/helm/vault-values.yaml +127 -0
- package/package.json +45 -0
- package/progress.md +156 -0
- package/scripts/.vault-init-keys.json +23 -0
- package/scripts/backfill-ownership.ts +113 -0
- package/scripts/finalize-mongo-auth.sh +212 -0
- package/scripts/setup-ipset.sh +107 -0
- package/scripts/setup-mongo-auth.sh +163 -0
- package/scripts/setup-neo4j-auth.sh +62 -0
- package/scripts/setup-redis-auth.sh +55 -0
- package/scripts/setup-registry-secret.sh +71 -0
- package/scripts/setup-vault.sh +308 -0
- package/src/config/env.ts +117 -0
- package/src/index.ts +153 -0
- package/src/middleware/auth.ts +294 -0
- package/src/middleware/org-access.ts +126 -0
- package/src/models/types.ts +288 -0
- package/src/routes/ai.ts +115 -0
- package/src/routes/audit.ts +121 -0
- package/src/routes/builds.ts +320 -0
- package/src/routes/cluster.ts +235 -0
- package/src/routes/config.ts +369 -0
- package/src/routes/databases.ts +201 -0
- package/src/routes/db-whitelist.ts +204 -0
- package/src/routes/domains.ts +547 -0
- package/src/routes/oauth.ts +195 -0
- package/src/routes/observability.ts +205 -0
- package/src/routes/orgs.ts +330 -0
- package/src/routes/platform.ts +134 -0
- package/src/routes/rotation.ts +191 -0
- package/src/routes/services.ts +290 -0
- package/src/routes/storage.ts +153 -0
- package/src/routes/users.ts +235 -0
- package/src/routes/webhooks.ts +384 -0
- package/src/services/__tests__/catalog-storage.test.ts +186 -0
- package/src/services/__tests__/deploy-pipeline.integration.test.ts +624 -0
- package/src/services/__tests__/pod-diagnostics.test.ts +332 -0
- package/src/services/__tests__/storage-credential-manager.test.ts +129 -0
- package/src/services/__tests__/storage-provisioner.test.ts +166 -0
- package/src/services/__tests__/troubleshooter.test.ts +329 -0
- package/src/services/bio-client.ts +189 -0
- package/src/services/build-queue.ts +137 -0
- package/src/services/builder.ts +1800 -0
- package/src/services/catalog.test.ts +1389 -0
- package/src/services/catalog.ts +1187 -0
- package/src/services/cloudflare.ts +259 -0
- package/src/services/config-validator.test.ts +190 -0
- package/src/services/config-validator.ts +108 -0
- package/src/services/crypto.ts +78 -0
- package/src/services/database.ts +122 -0
- package/src/services/db-credential-manager.test.ts +101 -0
- package/src/services/db-credential-manager.ts +447 -0
- package/src/services/db-provisioner.test.ts +602 -0
- package/src/services/db-provisioner.ts +589 -0
- package/src/services/db-whitelist.test.ts +671 -0
- package/src/services/db-whitelist.ts +496 -0
- package/src/services/dependency-resolver.test.ts +677 -0
- package/src/services/dependency-resolver.ts +319 -0
- package/src/services/deploy-gate.test.ts +247 -0
- package/src/services/deploy-gate.ts +75 -0
- package/src/services/dockerfile-generator.test.ts +401 -0
- package/src/services/dockerfile-generator.ts +606 -0
- package/src/services/forgejo.ts +212 -0
- package/src/services/koko.ts +492 -0
- package/src/services/kubernetes.ts +141 -0
- package/src/services/oauth-provisioner.test.ts +477 -0
- package/src/services/oauth-provisioner.ts +286 -0
- package/src/services/pod-diagnostics.ts +261 -0
- package/src/services/rotation-scheduler.ts +293 -0
- package/src/services/storage-credential-manager.ts +223 -0
- package/src/services/storage-provisioner.ts +216 -0
- package/src/services/storage.ts +274 -0
- package/src/services/troubleshooter.ts +208 -0
- package/src/services/vault-client.test.ts +272 -0
- package/src/services/vault-client.ts +587 -0
- package/src/utils/logger.ts +6 -0
- package/src/utils/response.ts +23 -0
- package/task_plan.md +171 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { apiResponse, apiError } from '../utils/response.js';
|
|
4
|
+
import { getBioClient, extractRequestMeta } from '../services/bio-client.js';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
export const oauthRouter = Router();
|
|
7
|
+
// Zod schemas for first-pass validation
|
|
8
|
+
const CreateClientSchema = z.object({
|
|
9
|
+
name: z.string().min(1).max(100),
|
|
10
|
+
description: z.string().optional(),
|
|
11
|
+
redirectUris: z.array(z.string().url()).min(1),
|
|
12
|
+
allowedScopes: z.array(z.string()).default(['openid', 'profile', 'email']),
|
|
13
|
+
allowedGrantTypes: z.array(z.string()).default(['authorization_code', 'refresh_token']),
|
|
14
|
+
isConfidential: z.boolean().default(true),
|
|
15
|
+
accessTokenTtl: z.number().int().min(60).max(86400).optional(),
|
|
16
|
+
refreshTokenTtl: z.number().int().min(3600).max(2592000).optional(),
|
|
17
|
+
});
|
|
18
|
+
const UpdateClientSchema = z.object({
|
|
19
|
+
name: z.string().min(1).max(100).optional(),
|
|
20
|
+
description: z.string().optional(),
|
|
21
|
+
redirectUris: z.array(z.string().url()).optional(),
|
|
22
|
+
allowedScopes: z.array(z.string()).optional(),
|
|
23
|
+
allowedGrantTypes: z.array(z.string()).optional(),
|
|
24
|
+
isActive: z.boolean().optional(),
|
|
25
|
+
accessTokenTtl: z.number().int().min(60).max(86400).optional(),
|
|
26
|
+
refreshTokenTtl: z.number().int().min(3600).max(2592000).optional(),
|
|
27
|
+
});
|
|
28
|
+
function handleUpstreamResponse(res, result, successStatus = 200) {
|
|
29
|
+
if (result.success) {
|
|
30
|
+
res.status(successStatus).json(apiResponse(result.data));
|
|
31
|
+
}
|
|
32
|
+
else if (result.error?.code === 'UPSTREAM_ERROR') {
|
|
33
|
+
res.status(502).json(apiError('UPSTREAM_ERROR', result.error.message));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const status = result.error?.code === 'NOT_FOUND' ? 404
|
|
37
|
+
: result.error?.code === 'CONFLICT' ? 409
|
|
38
|
+
: result.error?.code === 'UNAUTHORIZED' ? 401
|
|
39
|
+
: result.error?.code === 'FORBIDDEN' ? 403
|
|
40
|
+
: result.error?.code === 'VALIDATION_ERROR' ? 400
|
|
41
|
+
: 500;
|
|
42
|
+
res.status(status).json(apiError(result.error?.code || 'ERROR', result.error?.message || 'Unknown error'));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// List all OAuth clients
|
|
46
|
+
oauthRouter.get('/clients', async (req, res, next) => {
|
|
47
|
+
try {
|
|
48
|
+
const bio = getBioClient();
|
|
49
|
+
const meta = extractRequestMeta(req);
|
|
50
|
+
const result = await bio.listClients(meta);
|
|
51
|
+
handleUpstreamResponse(res, result);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
next(err);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// Get single OAuth client
|
|
58
|
+
oauthRouter.get('/clients/:clientId', async (req, res, next) => {
|
|
59
|
+
try {
|
|
60
|
+
const { clientId } = req.params;
|
|
61
|
+
const bio = getBioClient();
|
|
62
|
+
const meta = extractRequestMeta(req);
|
|
63
|
+
const result = await bio.getClient(clientId, meta);
|
|
64
|
+
handleUpstreamResponse(res, result);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
next(err);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// Create new OAuth client
|
|
71
|
+
oauthRouter.post('/clients', async (req, res, next) => {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = CreateClientSchema.safeParse(req.body);
|
|
74
|
+
if (!parsed.success) {
|
|
75
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'Invalid request', parsed.error.flatten()));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const bio = getBioClient();
|
|
79
|
+
const meta = extractRequestMeta(req);
|
|
80
|
+
const result = await bio.createClient(parsed.data, meta);
|
|
81
|
+
logger.info({ name: parsed.data.name }, 'OAuth client creation proxied to bio-id');
|
|
82
|
+
handleUpstreamResponse(res, result, 201);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
next(err);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// Update OAuth client
|
|
89
|
+
oauthRouter.patch('/clients/:clientId', async (req, res, next) => {
|
|
90
|
+
try {
|
|
91
|
+
const { clientId } = req.params;
|
|
92
|
+
const parsed = UpdateClientSchema.safeParse(req.body);
|
|
93
|
+
if (!parsed.success) {
|
|
94
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'Invalid request', parsed.error.flatten()));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const bio = getBioClient();
|
|
98
|
+
const meta = extractRequestMeta(req);
|
|
99
|
+
const result = await bio.updateClient(clientId, parsed.data, meta);
|
|
100
|
+
logger.info({ clientId }, 'OAuth client update proxied to bio-id');
|
|
101
|
+
handleUpstreamResponse(res, result);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
next(err);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// Add redirect URI to client
|
|
108
|
+
oauthRouter.post('/clients/:clientId/redirect-uris', async (req, res, next) => {
|
|
109
|
+
try {
|
|
110
|
+
const { clientId } = req.params;
|
|
111
|
+
const { uri } = req.body;
|
|
112
|
+
if (!uri || typeof uri !== 'string') {
|
|
113
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'URI is required'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
new URL(uri);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'Invalid URI format'));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const bio = getBioClient();
|
|
124
|
+
const meta = extractRequestMeta(req);
|
|
125
|
+
const result = await bio.addRedirectUri(clientId, uri, meta);
|
|
126
|
+
logger.info({ clientId, uri }, 'Redirect URI add proxied to bio-id');
|
|
127
|
+
handleUpstreamResponse(res, result);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
next(err);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// Remove redirect URI from client
|
|
134
|
+
oauthRouter.delete('/clients/:clientId/redirect-uris', async (req, res, next) => {
|
|
135
|
+
try {
|
|
136
|
+
const { clientId } = req.params;
|
|
137
|
+
const { uri } = req.body;
|
|
138
|
+
if (!uri || typeof uri !== 'string') {
|
|
139
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'URI is required'));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const bio = getBioClient();
|
|
143
|
+
const meta = extractRequestMeta(req);
|
|
144
|
+
const result = await bio.removeRedirectUri(clientId, uri, meta);
|
|
145
|
+
logger.info({ clientId, uri }, 'Redirect URI remove proxied to bio-id');
|
|
146
|
+
handleUpstreamResponse(res, result);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
next(err);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// Regenerate client secret
|
|
153
|
+
oauthRouter.post('/clients/:clientId/regenerate-secret', async (req, res, next) => {
|
|
154
|
+
try {
|
|
155
|
+
const { clientId } = req.params;
|
|
156
|
+
const bio = getBioClient();
|
|
157
|
+
const meta = extractRequestMeta(req);
|
|
158
|
+
const result = await bio.regenerateSecret(clientId, meta);
|
|
159
|
+
logger.info({ clientId }, 'OAuth client secret regeneration proxied to bio-id');
|
|
160
|
+
handleUpstreamResponse(res, result);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
next(err);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
// Delete OAuth client
|
|
167
|
+
oauthRouter.delete('/clients/:clientId', async (req, res, next) => {
|
|
168
|
+
try {
|
|
169
|
+
const { clientId } = req.params;
|
|
170
|
+
const bio = getBioClient();
|
|
171
|
+
const meta = extractRequestMeta(req);
|
|
172
|
+
const result = await bio.deleteClient(clientId, meta);
|
|
173
|
+
logger.info({ clientId }, 'OAuth client deletion proxied to bio-id');
|
|
174
|
+
handleUpstreamResponse(res, result);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
next(err);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/routes/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../../src/routes/observability.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,mBAAmB,4CAAW,CAAA"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { apiResponse, apiError } from '../utils/response.js';
|
|
5
|
+
import { requireOrgAccess } from '../middleware/org-access.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
import { getPods } from '../services/pod-diagnostics.js';
|
|
8
|
+
import { troubleshoot } from '../services/troubleshooter.js';
|
|
9
|
+
import { getServicesCollection } from '../services/database.js';
|
|
10
|
+
export const observabilityRouter = Router();
|
|
11
|
+
const EnvironmentSchema = z.object({
|
|
12
|
+
environment: z.enum(['prod', 'sandbox', 'uat']).default('sandbox'),
|
|
13
|
+
});
|
|
14
|
+
const K8S_NAME_REGEX = /^[a-z0-9][a-z0-9._-]{0,252}$/;
|
|
15
|
+
const LogQuerySchema = z.object({
|
|
16
|
+
environment: z.enum(['prod', 'sandbox', 'uat']).default('sandbox'),
|
|
17
|
+
previous: z.enum(['true', 'false']).default('false'),
|
|
18
|
+
tail: z.string().default('100').transform(Number).pipe(z.number().int().min(1).max(1000)),
|
|
19
|
+
container: z.string().regex(K8S_NAME_REGEX).optional(),
|
|
20
|
+
});
|
|
21
|
+
async function resolveService(req) {
|
|
22
|
+
if (req.service)
|
|
23
|
+
return req.service;
|
|
24
|
+
const serviceName = req.params.serviceName;
|
|
25
|
+
if (!serviceName)
|
|
26
|
+
return null;
|
|
27
|
+
const services = getServicesCollection();
|
|
28
|
+
return services.findOne({ $or: [{ id: serviceName }, { name: serviceName }] });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* GET /services/:serviceName/pods
|
|
32
|
+
* Returns pod list with status, restartCount, containerStatuses, recent events.
|
|
33
|
+
*/
|
|
34
|
+
observabilityRouter.get('/:serviceName/pods', requireOrgAccess(), async (req, res, next) => {
|
|
35
|
+
try {
|
|
36
|
+
const service = await resolveService(req);
|
|
37
|
+
if (!service) {
|
|
38
|
+
res.status(404).json(apiError('NOT_FOUND', 'Service not found'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const query = EnvironmentSchema.safeParse(req.query);
|
|
42
|
+
if (!query.success) {
|
|
43
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'Invalid query', query.error.flatten()));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const namespace = service.namespace || `${service.name}-${query.data.environment}`;
|
|
47
|
+
const pods = await getPods(namespace, service.name);
|
|
48
|
+
res.json(apiResponse({
|
|
49
|
+
service: service.name,
|
|
50
|
+
namespace,
|
|
51
|
+
environment: query.data.environment,
|
|
52
|
+
pods,
|
|
53
|
+
count: pods.length,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
next(err);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
/**
|
|
61
|
+
* GET /services/:serviceName/pods/logs
|
|
62
|
+
* SSE stream of pod logs via kubectl logs --follow.
|
|
63
|
+
*/
|
|
64
|
+
observabilityRouter.get('/:serviceName/pods/logs', requireOrgAccess(), async (req, res, next) => {
|
|
65
|
+
try {
|
|
66
|
+
const service = await resolveService(req);
|
|
67
|
+
if (!service) {
|
|
68
|
+
res.status(404).json(apiError('NOT_FOUND', 'Service not found'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const query = LogQuerySchema.safeParse(req.query);
|
|
72
|
+
if (!query.success) {
|
|
73
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'Invalid query', query.error.flatten()));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const namespace = service.namespace || `${service.name}-${query.data.environment}`;
|
|
77
|
+
const usePrevious = query.data.previous === 'true';
|
|
78
|
+
const tail = Math.min(Math.max(query.data.tail, 1), 1000);
|
|
79
|
+
const args = ['logs', '-n', namespace, '-l', `app=${service.name}`, `--tail=${tail}`];
|
|
80
|
+
if (!usePrevious) {
|
|
81
|
+
args.push('--follow');
|
|
82
|
+
}
|
|
83
|
+
if (usePrevious) {
|
|
84
|
+
args.push('--previous');
|
|
85
|
+
}
|
|
86
|
+
if (query.data.container) {
|
|
87
|
+
args.push('-c', query.data.container);
|
|
88
|
+
}
|
|
89
|
+
res.writeHead(200, {
|
|
90
|
+
'Content-Type': 'text/event-stream',
|
|
91
|
+
'Cache-Control': 'no-cache',
|
|
92
|
+
'Connection': 'keep-alive',
|
|
93
|
+
'X-Accel-Buffering': 'no',
|
|
94
|
+
});
|
|
95
|
+
res.write(': connected\n\n');
|
|
96
|
+
const kubectl = spawn('kubectl', args);
|
|
97
|
+
// Keepalive every 30 seconds to prevent proxy timeouts
|
|
98
|
+
const keepalive = setInterval(() => {
|
|
99
|
+
res.write(': keepalive\n\n');
|
|
100
|
+
}, 30_000);
|
|
101
|
+
// Safety timeout: close after 10 minutes
|
|
102
|
+
const timeout = setTimeout(() => {
|
|
103
|
+
kubectl.kill('SIGTERM');
|
|
104
|
+
res.write(`data: ${JSON.stringify({ event: 'timeout', message: 'Stream closed after 10 minutes' })}\n\n`);
|
|
105
|
+
res.end();
|
|
106
|
+
}, 10 * 60 * 1000);
|
|
107
|
+
const cleanup = () => {
|
|
108
|
+
clearInterval(keepalive);
|
|
109
|
+
clearTimeout(timeout);
|
|
110
|
+
};
|
|
111
|
+
kubectl.stdout.on('data', (chunk) => {
|
|
112
|
+
const lines = chunk.toString().split('\n').filter((l) => l.trim());
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
res.write(`data: ${JSON.stringify({ log: line, timestamp: new Date().toISOString() })}\n\n`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
kubectl.stderr.on('data', (chunk) => {
|
|
118
|
+
const msg = chunk.toString().trim();
|
|
119
|
+
if (msg) {
|
|
120
|
+
res.write(`data: ${JSON.stringify({ error: msg })}\n\n`);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
kubectl.on('close', (code) => {
|
|
124
|
+
cleanup();
|
|
125
|
+
res.write(`data: ${JSON.stringify({ event: 'close', code })}\n\n`);
|
|
126
|
+
res.end();
|
|
127
|
+
});
|
|
128
|
+
kubectl.on('error', (err) => {
|
|
129
|
+
logger.warn({ err, namespace, service: service.name }, 'kubectl logs spawn error');
|
|
130
|
+
cleanup();
|
|
131
|
+
res.write(`data: ${JSON.stringify({ error: err.message })}\n\n`);
|
|
132
|
+
res.end();
|
|
133
|
+
});
|
|
134
|
+
req.on('close', () => {
|
|
135
|
+
cleanup();
|
|
136
|
+
kubectl.kill('SIGTERM');
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
next(err);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
/**
|
|
144
|
+
* GET /services/:serviceName/troubleshoot
|
|
145
|
+
* Structured diagnostic analysis with actionable suggestions.
|
|
146
|
+
*/
|
|
147
|
+
observabilityRouter.get('/:serviceName/troubleshoot', requireOrgAccess(), async (req, res, next) => {
|
|
148
|
+
try {
|
|
149
|
+
const service = await resolveService(req);
|
|
150
|
+
if (!service) {
|
|
151
|
+
res.status(404).json(apiError('NOT_FOUND', 'Service not found'));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const query = EnvironmentSchema.safeParse(req.query);
|
|
155
|
+
if (!query.success) {
|
|
156
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'Invalid query', query.error.flatten()));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const namespace = service.namespace || `${service.name}-${query.data.environment}`;
|
|
160
|
+
const result = await troubleshoot(service.name, namespace, query.data.environment);
|
|
161
|
+
res.json(apiResponse(result));
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
next(err);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=observability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observability.js","sourceRoot":"","sources":["../../src/routes/observability.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgs.d.ts","sourceRoot":"","sources":["../../src/routes/orgs.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,4CAAW,CAAA"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
import { apiResponse, apiError } from '../utils/response.js';
|
|
5
|
+
import { env } from '../config/env.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
import { deriveOrgRole } from '../middleware/auth.js';
|
|
8
|
+
import { getOrgInvitesCollection } from '../services/database.js';
|
|
9
|
+
import { forgejoAdminRequest, deriveUsername, ensureOrgMembership, } from '../services/forgejo.js';
|
|
10
|
+
export const orgsRouter = Router();
|
|
11
|
+
/**
|
|
12
|
+
* Generate a human-readable invite code: WORD-WORD-WORD-WORD
|
|
13
|
+
*/
|
|
14
|
+
function generateInviteCode() {
|
|
15
|
+
const words = [
|
|
16
|
+
'alpha', 'bravo', 'cedar', 'delta', 'eagle', 'flame', 'grove', 'harbor',
|
|
17
|
+
'ivory', 'jasper', 'karma', 'lunar', 'maple', 'noble', 'orbit', 'prism',
|
|
18
|
+
'quartz', 'ridge', 'solar', 'terra', 'ultra', 'vivid', 'wave', 'xenon',
|
|
19
|
+
'yield', 'zenith', 'atlas', 'blade', 'coral', 'drift', 'ember', 'forge',
|
|
20
|
+
];
|
|
21
|
+
const parts = Array.from({ length: 4 }, () => words[randomBytes(1)[0] % words.length]);
|
|
22
|
+
return parts.join('-').toUpperCase();
|
|
23
|
+
}
|
|
24
|
+
const CreateInviteSchema = z.object({
|
|
25
|
+
maxUses: z.number().int().min(0).max(1000).default(0),
|
|
26
|
+
expiresInDays: z.number().int().min(1).max(90).default(7),
|
|
27
|
+
});
|
|
28
|
+
const JoinOrgSchema = z.object({
|
|
29
|
+
code: z.string().min(1).max(100),
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* POST /orgs/:org/invites
|
|
33
|
+
* Create an invite code for an org. Only org admins can create invites.
|
|
34
|
+
*/
|
|
35
|
+
orgsRouter.post('/:org/invites', async (req, res, next) => {
|
|
36
|
+
try {
|
|
37
|
+
const authReq = req;
|
|
38
|
+
const { org } = req.params;
|
|
39
|
+
if (!authReq.user) {
|
|
40
|
+
res.status(401).json(apiError('UNAUTHORIZED', 'Authentication required'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Must be admin of the target org (or platform service)
|
|
44
|
+
const role = deriveOrgRole(authReq.user.roles);
|
|
45
|
+
const isOwnOrg = authReq.user.org === org;
|
|
46
|
+
if (role !== 'admin' && !authReq.user.roles.includes('service')) {
|
|
47
|
+
res.status(403).json(apiError('FORBIDDEN', 'Only org admins can create invites'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!isOwnOrg && !authReq.user.roles.includes('service') && !authReq.user.roles.includes('super_admin')) {
|
|
51
|
+
res.status(403).json(apiError('FORBIDDEN', 'Cannot create invites for other orgs'));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const parsed = CreateInviteSchema.safeParse(req.body);
|
|
55
|
+
if (!parsed.success) {
|
|
56
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'Invalid request body', parsed.error.flatten()));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const { maxUses, expiresInDays } = parsed.data;
|
|
60
|
+
const now = new Date();
|
|
61
|
+
const expiresAt = new Date(now.getTime() + expiresInDays * 24 * 60 * 60 * 1000);
|
|
62
|
+
const invite = {
|
|
63
|
+
id: randomBytes(16).toString('hex'),
|
|
64
|
+
org,
|
|
65
|
+
code: generateInviteCode(),
|
|
66
|
+
createdBy: authReq.user.email,
|
|
67
|
+
createdAt: now.toISOString(),
|
|
68
|
+
expiresAt: expiresAt.toISOString(),
|
|
69
|
+
maxUses,
|
|
70
|
+
uses: 0,
|
|
71
|
+
usedBy: [],
|
|
72
|
+
};
|
|
73
|
+
const collection = getOrgInvitesCollection();
|
|
74
|
+
await collection.insertOne(invite);
|
|
75
|
+
logger.info({ org, code: invite.code, createdBy: invite.createdBy, expiresInDays }, 'Org invite created');
|
|
76
|
+
res.status(201).json(apiResponse({
|
|
77
|
+
code: invite.code,
|
|
78
|
+
org: invite.org,
|
|
79
|
+
expiresAt: invite.expiresAt,
|
|
80
|
+
maxUses: invite.maxUses,
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
next(err);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
/**
|
|
88
|
+
* GET /orgs/:org/invites
|
|
89
|
+
* List active invites for an org. Only org admins.
|
|
90
|
+
*/
|
|
91
|
+
orgsRouter.get('/:org/invites', async (req, res, next) => {
|
|
92
|
+
try {
|
|
93
|
+
const authReq = req;
|
|
94
|
+
const { org } = req.params;
|
|
95
|
+
if (!authReq.user) {
|
|
96
|
+
res.status(401).json(apiError('UNAUTHORIZED', 'Authentication required'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const role = deriveOrgRole(authReq.user.roles);
|
|
100
|
+
const isOwnOrg = authReq.user.org === org;
|
|
101
|
+
if (role !== 'admin' && !authReq.user.roles.includes('service')) {
|
|
102
|
+
res.status(403).json(apiError('FORBIDDEN', 'Only org admins can list invites'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!isOwnOrg && !authReq.user.roles.includes('service') && !authReq.user.roles.includes('super_admin')) {
|
|
106
|
+
res.status(403).json(apiError('FORBIDDEN', 'Cannot list invites for other orgs'));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const collection = getOrgInvitesCollection();
|
|
110
|
+
const invites = await collection
|
|
111
|
+
.find({ org, expiresAt: { $gt: new Date().toISOString() } })
|
|
112
|
+
.sort({ createdAt: -1 })
|
|
113
|
+
.toArray();
|
|
114
|
+
const sanitized = invites.map(inv => ({
|
|
115
|
+
code: inv.code,
|
|
116
|
+
org: inv.org,
|
|
117
|
+
createdBy: inv.createdBy,
|
|
118
|
+
createdAt: inv.createdAt,
|
|
119
|
+
expiresAt: inv.expiresAt,
|
|
120
|
+
maxUses: inv.maxUses,
|
|
121
|
+
uses: inv.uses,
|
|
122
|
+
}));
|
|
123
|
+
res.json(apiResponse(sanitized));
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
next(err);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
/**
|
|
130
|
+
* DELETE /orgs/:org/invites/:code
|
|
131
|
+
* Revoke an invite. Only org admins.
|
|
132
|
+
*/
|
|
133
|
+
orgsRouter.delete('/:org/invites/:code', async (req, res, next) => {
|
|
134
|
+
try {
|
|
135
|
+
const authReq = req;
|
|
136
|
+
const { org, code } = req.params;
|
|
137
|
+
if (!authReq.user) {
|
|
138
|
+
res.status(401).json(apiError('UNAUTHORIZED', 'Authentication required'));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const role = deriveOrgRole(authReq.user.roles);
|
|
142
|
+
const isOwnOrg = authReq.user.org === org;
|
|
143
|
+
if (role !== 'admin' && !authReq.user.roles.includes('service')) {
|
|
144
|
+
res.status(403).json(apiError('FORBIDDEN', 'Only org admins can revoke invites'));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!isOwnOrg && !authReq.user.roles.includes('service') && !authReq.user.roles.includes('super_admin')) {
|
|
148
|
+
res.status(403).json(apiError('FORBIDDEN', 'Cannot revoke invites for other orgs'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const collection = getOrgInvitesCollection();
|
|
152
|
+
const result = await collection.deleteOne({ org, code: code.toUpperCase() });
|
|
153
|
+
if (result.deletedCount === 0) {
|
|
154
|
+
res.status(404).json(apiError('NOT_FOUND', 'Invite not found'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
logger.info({ org, code, revokedBy: authReq.user.email }, 'Org invite revoked');
|
|
158
|
+
res.json(apiResponse({ revoked: true }));
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
next(err);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
/**
|
|
165
|
+
* POST /orgs/join
|
|
166
|
+
* Accept an invite code and join the org in Forgejo.
|
|
167
|
+
* Requires an existing Forgejo account (run provision-git first).
|
|
168
|
+
*/
|
|
169
|
+
orgsRouter.post('/join', async (req, res, next) => {
|
|
170
|
+
try {
|
|
171
|
+
const authReq = req;
|
|
172
|
+
if (!authReq.user) {
|
|
173
|
+
res.status(401).json(apiError('UNAUTHORIZED', 'Authentication required'));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!env.FORGEJO_TOKEN) {
|
|
177
|
+
res.status(503).json(apiError('SERVICE_UNAVAILABLE', 'Git provisioning is not configured'));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const parsed = JoinOrgSchema.safeParse(req.body);
|
|
181
|
+
if (!parsed.success) {
|
|
182
|
+
res.status(400).json(apiError('VALIDATION_ERROR', 'Invalid request body', parsed.error.flatten()));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const code = parsed.data.code.toUpperCase();
|
|
186
|
+
const collection = getOrgInvitesCollection();
|
|
187
|
+
// Find and validate the invite
|
|
188
|
+
const invite = await collection.findOne({ code });
|
|
189
|
+
if (!invite) {
|
|
190
|
+
res.status(404).json(apiError('INVITE_NOT_FOUND', 'Invalid or expired invite code'));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (new Date(invite.expiresAt) < new Date()) {
|
|
194
|
+
res.status(410).json(apiError('INVITE_EXPIRED', 'This invite has expired'));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (invite.maxUses > 0 && invite.uses >= invite.maxUses) {
|
|
198
|
+
res.status(410).json(apiError('INVITE_EXHAUSTED', 'This invite has reached its usage limit'));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (invite.usedBy.includes(authReq.user.email)) {
|
|
202
|
+
res.status(409).json(apiError('ALREADY_JOINED', 'You have already used this invite'));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Resolve the user's Forgejo username
|
|
206
|
+
const username = deriveUsername(authReq.user.email);
|
|
207
|
+
const userCheck = await forgejoAdminRequest('GET', `/users/${encodeURIComponent(username)}`);
|
|
208
|
+
if (userCheck.status !== 200 || !userCheck.data) {
|
|
209
|
+
res.status(400).json(apiError('NO_GIT_ACCOUNT', 'No Forgejo account found. Run `tawa login` first.'));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const forgejoUsername = userCheck.data.login;
|
|
213
|
+
// Add user to the org
|
|
214
|
+
const membership = await ensureOrgMembership(invite.org, forgejoUsername);
|
|
215
|
+
if (!membership) {
|
|
216
|
+
res.status(500).json(apiError('JOIN_FAILED', 'Failed to add you to the org'));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Record usage
|
|
220
|
+
await collection.updateOne({ code }, {
|
|
221
|
+
$inc: { uses: 1 },
|
|
222
|
+
$push: { usedBy: authReq.user.email },
|
|
223
|
+
});
|
|
224
|
+
logger.info({ org: invite.org, username: forgejoUsername, email: authReq.user.email, code }, 'User joined org via invite');
|
|
225
|
+
res.json(apiResponse({
|
|
226
|
+
org: invite.org,
|
|
227
|
+
username: forgejoUsername,
|
|
228
|
+
role: membership.role,
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
next(err);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
/**
|
|
236
|
+
* GET /orgs/:org/members
|
|
237
|
+
* List members of a Forgejo org. Org members can view.
|
|
238
|
+
*/
|
|
239
|
+
orgsRouter.get('/:org/members', async (req, res, next) => {
|
|
240
|
+
try {
|
|
241
|
+
const authReq = req;
|
|
242
|
+
const { org } = req.params;
|
|
243
|
+
if (!authReq.user) {
|
|
244
|
+
res.status(401).json(apiError('UNAUTHORIZED', 'Authentication required'));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (!env.FORGEJO_TOKEN) {
|
|
248
|
+
res.status(503).json(apiError('SERVICE_UNAVAILABLE', 'Git provisioning is not configured'));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const result = await forgejoAdminRequest('GET', `/orgs/${encodeURIComponent(org)}/members`);
|
|
252
|
+
if (result.status === 404) {
|
|
253
|
+
res.status(404).json(apiError('NOT_FOUND', 'Org not found'));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (result.status !== 200 || !result.data) {
|
|
257
|
+
res.status(502).json(apiError('UPSTREAM_ERROR', 'Failed to fetch org members'));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const members = result.data.map(m => ({
|
|
261
|
+
username: m.login,
|
|
262
|
+
id: m.id,
|
|
263
|
+
}));
|
|
264
|
+
res.json(apiResponse(members));
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
next(err);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
//# sourceMappingURL=orgs.js.map
|