prow-authority 3.6.3
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 +11 -0
- package/.dockerignore +11 -0
- package/.github/workflows/_deploy.yml +191 -0
- package/.github/workflows/deploy-dev.yml +30 -0
- package/.github/workflows/deploy-prod.yml +34 -0
- package/CLAUDE.md +114 -0
- package/Dockerfile +61 -0
- package/Dockerfile.dev +24 -0
- package/README.md +1 -0
- package/package.json +61 -0
- package/src/App.ts +40 -0
- package/src/helpers/api-error.ts +21 -0
- package/src/helpers/authority.ts +216 -0
- package/src/helpers/config.ts +4 -0
- package/src/helpers/holded.ts +105 -0
- package/src/helpers/mysql.ts +15 -0
- package/src/helpers/notifications.ts +55 -0
- package/src/helpers/random.ts +18 -0
- package/src/helpers/s3.ts +43 -0
- package/src/helpers/stripe.ts +8 -0
- package/src/index.ts +47 -0
- package/src/v1/common-types.ts +12 -0
- package/src/v1/controllers/admin/accounting-seat-controller.ts +148 -0
- package/src/v1/controllers/admin/billing-cycle-controller.ts +171 -0
- package/src/v1/controllers/admin/invoice-controller.ts +222 -0
- package/src/v1/controllers/admin/license-controller.ts +190 -0
- package/src/v1/controllers/admin/license-limit-controller.ts +160 -0
- package/src/v1/controllers/admin/notification-controller.ts +115 -0
- package/src/v1/controllers/admin/organization-controller.ts +210 -0
- package/src/v1/controllers/admin/organization-license-controller.ts +224 -0
- package/src/v1/controllers/admin/organization-license-limit-controller.ts +196 -0
- package/src/v1/controllers/admin/organization-usage-controller.ts +167 -0
- package/src/v1/controllers/admin/payment-method-controller.ts +173 -0
- package/src/v1/controllers/admin/policy-controller.ts +138 -0
- package/src/v1/controllers/admin/policy-version-controller.ts +143 -0
- package/src/v1/controllers/admin/schedule-controller.ts +181 -0
- package/src/v1/controllers/admin/service-controller.ts +206 -0
- package/src/v1/controllers/admin/service-notification-controller.ts +166 -0
- package/src/v1/controllers/admin/user-controller.ts +282 -0
- package/src/v1/controllers/admin/user-organization-controller.ts +172 -0
- package/src/v1/controllers/admin/user-policy-controller.ts +164 -0
- package/src/v1/controllers/auth/@deprecated oauth-controller.ts +191 -0
- package/src/v1/controllers/auth/identity-token-controller.ts +286 -0
- package/src/v1/controllers/auth/organization-token-controller.ts +277 -0
- package/src/v1/controllers/auth/temp-code-controller.ts +60 -0
- package/src/v1/controllers/billing/billing-controller.ts +805 -0
- package/src/v1/controllers/organizations/public-organization-controller.ts +1796 -0
- package/src/v1/controllers/policies/public-policy-controller.ts +68 -0
- package/src/v1/controllers/services/public-service-controller.ts +177 -0
- package/src/v1/controllers/services/service-controller.ts +474 -0
- package/src/v1/controllers/users/public-user-controller.ts +471 -0
- package/src/v1/daos/@deprecated auth-dao.ts +173 -0
- package/src/v1/daos/accounting-seat-dao.ts +111 -0
- package/src/v1/daos/billing-cycle-dao.ts +107 -0
- package/src/v1/daos/invoice-dao.ts +131 -0
- package/src/v1/daos/license-dao.ts +152 -0
- package/src/v1/daos/license-limit-dao.ts +120 -0
- package/src/v1/daos/organization-dao.ts +109 -0
- package/src/v1/daos/organization-license-dao.ts +117 -0
- package/src/v1/daos/organization-license-limit-dao.ts +157 -0
- package/src/v1/daos/organization-usage-dao.ts +115 -0
- package/src/v1/daos/payment-method-dao.ts +110 -0
- package/src/v1/daos/policy-dao.ts +105 -0
- package/src/v1/daos/policy-version-dao.ts +112 -0
- package/src/v1/daos/redis-dao.ts +38 -0
- package/src/v1/daos/schema.sql +462 -0
- package/src/v1/daos/service-dao.ts +118 -0
- package/src/v1/daos/service-notification-dao.ts +120 -0
- package/src/v1/daos/user-dao.ts +127 -0
- package/src/v1/daos/user-organization-dao.ts +123 -0
- package/src/v1/daos/user-policy-dao.ts +109 -0
- package/src/v1/middlewares/auth/identity-token-middleware.ts +51 -0
- package/src/v1/middlewares/auth/organization-token-middleware.ts +73 -0
- package/src/v1/middlewares/auth/service-token-middleware.ts +167 -0
- package/src/v1/middlewares/authority-middleware.ts +97 -0
- package/src/v1/middlewares/organizations/organization-middleware.ts +94 -0
- package/src/v1/middlewares/static/static-middleware.ts +41 -0
- package/src/v1/middlewares/users/user-middleware.ts +41 -0
- package/src/v1/routes/admin/accounting-seat-router.ts +89 -0
- package/src/v1/routes/admin/admin-router.ts +61 -0
- package/src/v1/routes/admin/billing-cycle-router.ts +89 -0
- package/src/v1/routes/admin/invoice-router.ts +89 -0
- package/src/v1/routes/admin/license-limit-router.ts +89 -0
- package/src/v1/routes/admin/license-router.ts +89 -0
- package/src/v1/routes/admin/notification-router.ts +61 -0
- package/src/v1/routes/admin/organization-license-limit-router.ts +89 -0
- package/src/v1/routes/admin/organization-license-router.ts +89 -0
- package/src/v1/routes/admin/organization-router.ts +89 -0
- package/src/v1/routes/admin/payment-method-router.ts +89 -0
- package/src/v1/routes/admin/policy-router.ts +89 -0
- package/src/v1/routes/admin/policy-version-router.ts +89 -0
- package/src/v1/routes/admin/schedule-router.ts +75 -0
- package/src/v1/routes/admin/service-notification-router.ts +98 -0
- package/src/v1/routes/admin/service-router.ts +89 -0
- package/src/v1/routes/admin/user-organization-router.ts +89 -0
- package/src/v1/routes/admin/user-policy-router.ts +89 -0
- package/src/v1/routes/admin/user-router.ts +89 -0
- package/src/v1/routes/auth/@deprecated auth-router.ts +199 -0
- package/src/v1/routes/auth/identity-token-router.ts +147 -0
- package/src/v1/routes/auth/organization-token-router.ts +70 -0
- package/src/v1/routes/billing/billing-router.ts +227 -0
- package/src/v1/routes/organizations/invoices-router.ts +48 -0
- package/src/v1/routes/organizations/payments-router.ts +104 -0
- package/src/v1/routes/organizations/public-organization-router.ts +319 -0
- package/src/v1/routes/organizations/users-router.ts +139 -0
- package/src/v1/routes/policies/public-policy-router.ts +50 -0
- package/src/v1/routes/services/public-service-router.ts +89 -0
- package/src/v1/routes/services/service-router.ts +197 -0
- package/src/v1/routes/users/public-user-router.ts +230 -0
- package/src/v1/routes/v1-router.ts +50 -0
- package/src/v1/schemas/admin/accounting-seat-schemas.ts +22 -0
- package/src/v1/schemas/admin/billing-cycle-schemas.ts +20 -0
- package/src/v1/schemas/admin/invoice-schemas.ts +41 -0
- package/src/v1/schemas/admin/license-limit-schemas.ts +25 -0
- package/src/v1/schemas/admin/license-schemas.ts +30 -0
- package/src/v1/schemas/admin/organization-license-limit-schemas.ts +29 -0
- package/src/v1/schemas/admin/organization-license-schemas.ts +43 -0
- package/src/v1/schemas/admin/organization-schemas.ts +67 -0
- package/src/v1/schemas/admin/organization-usage-schemas.ts +15 -0
- package/src/v1/schemas/admin/payment-method-schemas.ts +40 -0
- package/src/v1/schemas/admin/policy-schemas.ts +31 -0
- package/src/v1/schemas/admin/service-notification-schemas.ts +23 -0
- package/src/v1/schemas/admin/service-schemas.ts +56 -0
- package/src/v1/schemas/admin/user-organization-schemas.ts +24 -0
- package/src/v1/schemas/admin/user-policy-schemas.ts +17 -0
- package/src/v1/schemas/admin/user-schemas.ts +52 -0
- package/src/v1/schemas/auth/auth-schemas.ts +55 -0
- package/src/v1/schemas/billing/billing-schemas.ts +32 -0
- package/src/v1/schemas/organizations/public-organization-schemas.ts +77 -0
- package/src/v1/schemas/payments/payment-method-schemas.ts +0 -0
- package/src/v1/schemas/services/public-service-schemas.ts +5 -0
- package/src/v1/schemas/services/service-schemas.ts +27 -0
- package/src/v1/schemas/users/public-user-schemas.ts +73 -0
- package/src/v1/types/admin/accounting-seat-types.ts +16 -0
- package/src/v1/types/admin/billing-cycle-types.ts +14 -0
- package/src/v1/types/admin/invoice-types.ts +39 -0
- package/src/v1/types/admin/license-limit-types.ts +16 -0
- package/src/v1/types/admin/license-types.ts +29 -0
- package/src/v1/types/admin/organization-license-limit-types.ts +13 -0
- package/src/v1/types/admin/organization-license-types.ts +21 -0
- package/src/v1/types/admin/organization-types.ts +60 -0
- package/src/v1/types/admin/organization-usage-types.ts +17 -0
- package/src/v1/types/admin/payment-method-types.ts +30 -0
- package/src/v1/types/admin/policy-types.ts +9 -0
- package/src/v1/types/admin/policy-version-types.ts +12 -0
- package/src/v1/types/admin/service-notification-types.ts +20 -0
- package/src/v1/types/admin/service-types.ts +36 -0
- package/src/v1/types/admin/user-organization-types.ts +31 -0
- package/src/v1/types/admin/user-types.ts +44 -0
- package/src/v1/types/auth/@deprecated auth-types.ts +26 -0
- package/src/v1/types/auth/common-types.ts +7 -0
- package/src/v1/types/auth/confirmation-types.ts +5 -0
- package/src/v1/types/auth/identity-token-types.ts +8 -0
- package/src/v1/types/auth/organization-token-types.ts +23 -0
- package/src/v1/types/auth/otp-types.ts +12 -0
- package/src/v1/types/billing/billing-types.ts +37 -0
- package/src/v1/types/billing/ue-types.ts +29 -0
- package/src/v1/types/organizations/public-organization-types.ts +76 -0
- package/src/v1/types/policies/public-policy-types.ts +7 -0
- package/src/v1/types/services/public-service-types.ts +20 -0
- package/src/v1/types/services/service-types.ts +43 -0
- package/src/v1/types/users/public-user-types.ts +15 -0
- package/src/v1/types/users/user-policy-types.ts +11 -0
- package/static/error.html +55 -0
- package/static/grant.html +117 -0
- package/static/login.html +54 -0
- package/static/oauth-client-logo.webp +0 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(xargs ls -la)",
|
|
5
|
+
"Read(//home/beseif/Documentos/Back/prow-feu/**)",
|
|
6
|
+
"Read(//home/beseif/Documentos/Back/prow-feu/src/**)",
|
|
7
|
+
"Bash(find /home/beseif/Documentos -name \"prow-notifications.yaml\" 2>/dev/null)",
|
|
8
|
+
"Read(//home/beseif/Documentos/**)"
|
|
9
|
+
]
|
|
10
|
+
}
|
|
11
|
+
}
|
package/.dockerignore
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
name: _deploy (reusable)
|
|
2
|
+
|
|
3
|
+
# Workflow reutilizable: build de imagen Docker + push a Amazon ECR.
|
|
4
|
+
#
|
|
5
|
+
# config/config.yaml sigue gestionandose exactamente igual que hoy: vive en
|
|
6
|
+
# el checkout de prow-configuration-files en la propia EC2 (mismo mecanismo
|
|
7
|
+
# de actualizacion que ya usa ese repo). Antes se conectaba con el proceso
|
|
8
|
+
# PM2 via symlink; con Docker, al tener el contenedor su propio filesystem
|
|
9
|
+
# aislado, un symlink que apunte fuera del mount no se resolveria dentro del
|
|
10
|
+
# contenedor. Por eso aqui se monta directamente el fichero real del
|
|
11
|
+
# checkout como bind-mount de solo lectura en el `docker run` (ver
|
|
12
|
+
# CONFIG_HOST_PATH). Ningun secreto de config pasa por GitHub Actions ni
|
|
13
|
+
# por AWS: no hace falta tocar IAM para eso.
|
|
14
|
+
#
|
|
15
|
+
# Variables de REPOSITORIO (mismo valor para dev y prod, no van en el
|
|
16
|
+
# Environment — misma cuenta/region de AWS para ambos entornos):
|
|
17
|
+
# ECR_REGISTRY — ej: 123456789.dkr.ecr.eu-west-1.amazonaws.com
|
|
18
|
+
# ECR_REPOSITORY — ej: prow/prow-authority
|
|
19
|
+
# AWS_REGION — ej: eu-west-1
|
|
20
|
+
# CONFIG_HOST_PATH — ruta al config.yaml real dentro del checkout de
|
|
21
|
+
# prow-configuration-files: /node/configuration-files/prow-authority.yaml
|
|
22
|
+
# Es la misma ruta en ambas EC2 — lo que cambia es la
|
|
23
|
+
# rama (develop/prod) checkouteada en cada maquina en
|
|
24
|
+
# esa misma ruta. (fichero, no carpeta — se monta 1:1
|
|
25
|
+
# sobre /app/config/config.yaml)
|
|
26
|
+
# Si algun dia dev/prod necesitan un valor distinto para alguna de estas
|
|
27
|
+
# (ej. prod en otra cuenta de AWS), basta con redefinirla tambien en el
|
|
28
|
+
# Environment correspondiente — una variable de Environment con el mismo
|
|
29
|
+
# nombre tiene prioridad sobre la de repositorio.
|
|
30
|
+
#
|
|
31
|
+
# Variables requeridas por GitHub Environment (dev/prod):
|
|
32
|
+
# INSTANCE_ID — id de la EC2 destino (i-xxxxxxxx)
|
|
33
|
+
# HOST_PORT — puerto del host mapeado al 5001 del contenedor
|
|
34
|
+
#
|
|
35
|
+
# Secrets requeridos (repositorio, ver _deploy.yml de prow-feu — misma cuenta AWS):
|
|
36
|
+
# AWS_ROLE_ARN — rol OIDC de CI con permisos ECR push + SSM SendCommand
|
|
37
|
+
# NPM_TOKEN — npm automation token para @beseif-solutions/*
|
|
38
|
+
#
|
|
39
|
+
# El llamador debe pasar `secrets: inherit`.
|
|
40
|
+
|
|
41
|
+
on:
|
|
42
|
+
workflow_call:
|
|
43
|
+
inputs:
|
|
44
|
+
environment:
|
|
45
|
+
description: GitHub Environment del que se leen secrets y variables (dev/prod)
|
|
46
|
+
required: true
|
|
47
|
+
type: string
|
|
48
|
+
image_tag:
|
|
49
|
+
description: >
|
|
50
|
+
Tag principal de la imagen. Dev usa "dev"; prod usa el tag de release (ej: "v1.2.3").
|
|
51
|
+
required: true
|
|
52
|
+
type: string
|
|
53
|
+
tag_latest:
|
|
54
|
+
description: Si true, publica tambien la etiqueta :latest (solo para prod).
|
|
55
|
+
required: false
|
|
56
|
+
type: boolean
|
|
57
|
+
default: false
|
|
58
|
+
|
|
59
|
+
permissions:
|
|
60
|
+
contents: read
|
|
61
|
+
id-token: write # requerido para OIDC (sts:AssumeRoleWithWebIdentity)
|
|
62
|
+
|
|
63
|
+
jobs:
|
|
64
|
+
build-and-push:
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
environment: ${{ inputs.environment }}
|
|
67
|
+
|
|
68
|
+
env:
|
|
69
|
+
ECR_REGISTRY: ${{ vars.ECR_REGISTRY }}
|
|
70
|
+
ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }}
|
|
71
|
+
|
|
72
|
+
steps:
|
|
73
|
+
- name: Checkout
|
|
74
|
+
uses: actions/checkout@v4
|
|
75
|
+
|
|
76
|
+
- name: Set up Docker Buildx
|
|
77
|
+
uses: docker/setup-buildx-action@v3
|
|
78
|
+
|
|
79
|
+
- name: Configure AWS credentials (OIDC)
|
|
80
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
81
|
+
with:
|
|
82
|
+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
|
83
|
+
aws-region: ${{ vars.AWS_REGION }}
|
|
84
|
+
|
|
85
|
+
- name: Login to Amazon ECR
|
|
86
|
+
uses: aws-actions/amazon-ecr-login@v2
|
|
87
|
+
|
|
88
|
+
# Construye la lista de tags segun el entorno:
|
|
89
|
+
# dev → :dev
|
|
90
|
+
# prod → :v1.2.3 + :latest
|
|
91
|
+
- name: Compute image tags
|
|
92
|
+
id: tags
|
|
93
|
+
run: |
|
|
94
|
+
BASE="${ECR_REGISTRY}/${ECR_REPOSITORY}"
|
|
95
|
+
TAGS="${BASE}:${{ inputs.image_tag }}"
|
|
96
|
+
if [ "${{ inputs.tag_latest }}" = "true" ]; then
|
|
97
|
+
TAGS="${TAGS}"$'\n'"${BASE}:latest"
|
|
98
|
+
fi
|
|
99
|
+
echo "tags<<EOF" >> "$GITHUB_OUTPUT"
|
|
100
|
+
echo "${TAGS}" >> "$GITHUB_OUTPUT"
|
|
101
|
+
echo "EOF" >> "$GITHUB_OUTPUT"
|
|
102
|
+
|
|
103
|
+
- name: Write build secrets to files
|
|
104
|
+
run: printf '%s' "${NPM_TOKEN}" > /tmp/npm_token
|
|
105
|
+
env:
|
|
106
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
107
|
+
|
|
108
|
+
- name: Build and push
|
|
109
|
+
uses: docker/build-push-action@v6
|
|
110
|
+
with:
|
|
111
|
+
context: .
|
|
112
|
+
push: true
|
|
113
|
+
tags: ${{ steps.tags.outputs.tags }}
|
|
114
|
+
secret-files: |
|
|
115
|
+
npm_token=/tmp/npm_token
|
|
116
|
+
cache-from: type=gha
|
|
117
|
+
cache-to: type=gha,mode=max
|
|
118
|
+
|
|
119
|
+
# Ordena a la EC2 (por instance ID) que haga login en ECR, descargue la
|
|
120
|
+
# imagen nueva y reinicie el contenedor montando el config.yaml real
|
|
121
|
+
# del checkout de prow-configuration-files (CONFIG_HOST_PATH) como
|
|
122
|
+
# solo lectura. El instance profile de la EC2 ya tiene permisos ECR
|
|
123
|
+
# pull, no hace falta nada nuevo en IAM.
|
|
124
|
+
- name: Deploy to EC2 (SSM)
|
|
125
|
+
run: |
|
|
126
|
+
set -euo pipefail
|
|
127
|
+
|
|
128
|
+
BASE="${ECR_REGISTRY}/${ECR_REPOSITORY}"
|
|
129
|
+
IMAGE="${BASE}:${{ inputs.image_tag }}"
|
|
130
|
+
CONTAINER=$(basename "${ECR_REPOSITORY}")
|
|
131
|
+
|
|
132
|
+
REMOTE="
|
|
133
|
+
aws ecr get-login-password --region ${{ vars.AWS_REGION }} \
|
|
134
|
+
| docker login --username AWS --password-stdin ${ECR_REGISTRY} \
|
|
135
|
+
&& docker pull ${IMAGE} \
|
|
136
|
+
&& docker stop ${CONTAINER} || true \
|
|
137
|
+
&& docker rm ${CONTAINER} || true \
|
|
138
|
+
&& docker run -d \
|
|
139
|
+
--name ${CONTAINER} \
|
|
140
|
+
--restart unless-stopped \
|
|
141
|
+
-p ${{ vars.HOST_PORT }}:5001 \
|
|
142
|
+
-v ${{ vars.CONFIG_HOST_PATH }}:/app/config/config.yaml:ro \
|
|
143
|
+
${IMAGE}
|
|
144
|
+
"
|
|
145
|
+
B64=$(printf '%s' "${REMOTE}" | base64 -w0)
|
|
146
|
+
|
|
147
|
+
CMD_ID=$(aws ssm send-command \
|
|
148
|
+
--region ${{ vars.AWS_REGION }} \
|
|
149
|
+
--document-name AWS-RunShellScript \
|
|
150
|
+
--comment "deploy ${GITHUB_SHA::7} -> ${CONTAINER}:${{ inputs.image_tag }}" \
|
|
151
|
+
--targets "Key=instanceids,Values=${{ vars.INSTANCE_ID }}" \
|
|
152
|
+
--parameters "commands=[\"echo ${B64} | base64 -d | bash\"]" \
|
|
153
|
+
--query Command.CommandId --output text)
|
|
154
|
+
echo "SSM CommandId: ${CMD_ID}"
|
|
155
|
+
|
|
156
|
+
for _ in $(seq 1 60); do
|
|
157
|
+
STATUS=$(aws ssm list-command-invocations \
|
|
158
|
+
--region ${{ vars.AWS_REGION }} \
|
|
159
|
+
--command-id "${CMD_ID}" \
|
|
160
|
+
--query "CommandInvocations[0].Status" --output text 2>/dev/null || echo "Pending")
|
|
161
|
+
echo "Estado: ${STATUS}"
|
|
162
|
+
if echo "${STATUS}" | grep -qE 'Pending|InProgress|Delayed'; then
|
|
163
|
+
sleep 5; continue
|
|
164
|
+
fi
|
|
165
|
+
break
|
|
166
|
+
done
|
|
167
|
+
|
|
168
|
+
if [ "${STATUS}" != "Success" ]; then
|
|
169
|
+
echo "::error::Deploy fallido con estado: ${STATUS}"
|
|
170
|
+
aws ssm get-command-invocation \
|
|
171
|
+
--region ${{ vars.AWS_REGION }} \
|
|
172
|
+
--command-id "${CMD_ID}" \
|
|
173
|
+
--instance-id "${{ vars.INSTANCE_ID }}" \
|
|
174
|
+
--query "StandardErrorContent" --output text || true
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
echo "Deploy OK — ${CONTAINER}:${{ inputs.image_tag }}" >> "$GITHUB_STEP_SUMMARY"
|
|
178
|
+
|
|
179
|
+
- name: Summary
|
|
180
|
+
run: |
|
|
181
|
+
BASE="${ECR_REGISTRY}/${ECR_REPOSITORY}"
|
|
182
|
+
|
|
183
|
+
echo "### Imagen publicada y desplegada" >> "$GITHUB_STEP_SUMMARY"
|
|
184
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
185
|
+
echo "**Tags publicados:**" >> "$GITHUB_STEP_SUMMARY"
|
|
186
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
187
|
+
echo "${BASE}:${{ inputs.image_tag }}" >> "$GITHUB_STEP_SUMMARY"
|
|
188
|
+
if [ "${{ inputs.tag_latest }}" = "true" ]; then
|
|
189
|
+
echo "${BASE}:latest" >> "$GITHUB_STEP_SUMMARY"
|
|
190
|
+
fi
|
|
191
|
+
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Deploy Dev
|
|
2
|
+
|
|
3
|
+
# Publica la imagen Docker del entorno Dev en ECR con tag :dev y despliega
|
|
4
|
+
# en la EC2 de dev via SSM.
|
|
5
|
+
#
|
|
6
|
+
# Tags que genera:
|
|
7
|
+
# :dev — siempre apunta al ultimo build de develop
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches: [develop]
|
|
12
|
+
workflow_dispatch: {}
|
|
13
|
+
|
|
14
|
+
# Cancela despliegues solapados de Dev (un push nuevo invalida el anterior).
|
|
15
|
+
concurrency:
|
|
16
|
+
group: deploy-dev
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
id-token: write
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
deploy:
|
|
25
|
+
uses: ./.github/workflows/_deploy.yml
|
|
26
|
+
with:
|
|
27
|
+
environment: dev
|
|
28
|
+
image_tag: dev
|
|
29
|
+
tag_latest: false
|
|
30
|
+
secrets: inherit
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Deploy Prod
|
|
2
|
+
|
|
3
|
+
# Publica la imagen Docker del entorno Prod en ECR con el tag de release y
|
|
4
|
+
# despliega en la EC2 de prod via SSM.
|
|
5
|
+
# Se dispara al publicar un GitHub Release (tag v*) o manualmente.
|
|
6
|
+
# La aprobacion previa la impone la proteccion del environment `prod`
|
|
7
|
+
# (required reviewers).
|
|
8
|
+
#
|
|
9
|
+
# Tags que genera:
|
|
10
|
+
# :v1.2.3 — version exacta del release
|
|
11
|
+
# :latest — apunta siempre al ultimo release de produccion
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
release:
|
|
15
|
+
types: [published]
|
|
16
|
+
workflow_dispatch: {}
|
|
17
|
+
|
|
18
|
+
# En Prod NO cancelamos un despliegue en curso.
|
|
19
|
+
concurrency:
|
|
20
|
+
group: deploy-prod
|
|
21
|
+
cancel-in-progress: false
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
id-token: write
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
deploy:
|
|
29
|
+
uses: ./.github/workflows/_deploy.yml
|
|
30
|
+
with:
|
|
31
|
+
environment: prod
|
|
32
|
+
image_tag: ${{ github.ref_name }}
|
|
33
|
+
tag_latest: true
|
|
34
|
+
secrets: inherit
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this service is
|
|
6
|
+
|
|
7
|
+
`prow-authority` is the central auth/licensing/billing service for the Prow platform ("letsprow"). It is the
|
|
8
|
+
source of truth for users, organizations, memberships/roles, services, licenses/quotas, policies, notifications,
|
|
9
|
+
and billing (Stripe + Holded invoicing). Other Prow services integrate with it via the
|
|
10
|
+
`@beseif-solutions/prow-authority-for-services` client library (see `src/helpers/authority.ts`), which this
|
|
11
|
+
service also uses internally to check quotas/licensing and emit notification events.
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run build # tsc compile src/ -> dist/
|
|
17
|
+
npm run build_watch # tsc --watch
|
|
18
|
+
npm run clean # rimraf dist
|
|
19
|
+
npm run run # nodemon dist/index.js, watching dist/**/*.js (runs the already-built output)
|
|
20
|
+
npm run develop # clean + build + build_watch + run, in parallel (main dev loop)
|
|
21
|
+
npm run start # node dist/index.js (no watch, e.g. for prod)
|
|
22
|
+
npm run lint # eslint --ext .ts src --fix
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
There is no test runner configured in `package.json` — there are no automated tests in this repo currently.
|
|
26
|
+
|
|
27
|
+
A `pre-push` git hook (via husky) runs `npm run build`, so a broken TypeScript build blocks pushes.
|
|
28
|
+
|
|
29
|
+
The server reads its port and all external service credentials from `config/config.yaml` (loaded via
|
|
30
|
+
`@beseif-solutions/utility-functions`'s `YamlParser` in `src/helpers/config.ts`, exposed as `nconf`). There is a
|
|
31
|
+
single `config.yaml` (not environment-specific files), so changes to it affect whichever environment the running
|
|
32
|
+
instance points at — check `SERVICES:RDS:URL` / `SERVICES:AUTHORITY:HOST` before assuming which environment you're
|
|
33
|
+
touching.
|
|
34
|
+
|
|
35
|
+
## Architecture
|
|
36
|
+
|
|
37
|
+
Layered, per-resource structure under `src/v1/`, mirrored across five parallel folders — a new resource typically
|
|
38
|
+
touches one file in each:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
routes/<scope>/<resource>-router.ts Express Router: parses req, calls controller, maps result/errors to HTTP
|
|
42
|
+
controllers/<scope>/<resource>-controller.ts Business logic: validation, orchestration, calls DAOs/other controllers
|
|
43
|
+
daos/<resource>-dao.ts Raw MySQL access (mysql2-style, via @beseif-solutions/utility-functions MySQL)
|
|
44
|
+
schemas/<scope>/<resource>-schemas.ts Joi-like validation schemas (Schema.* from utility-functions) for add/update
|
|
45
|
+
types/<scope>/<resource>-types.ts Domain type (public shape) + DBRecord type (raw DB row shape)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Controllers, routers and DAOs are implemented as singleton classes exported as a default instance
|
|
49
|
+
(`class UserCtrl { ... }` → `export default new UserCtrl()`), not as plain functions/modules — follow this pattern
|
|
50
|
+
for new resources.
|
|
51
|
+
|
|
52
|
+
Every router action follows the same try/catch shape: call the controller, `res.json(response)` on success; on
|
|
53
|
+
error, `if (e instanceof APIError) res.status(e.code).json(e.data)` else 500 with `e.message`. `APIError`
|
|
54
|
+
(`src/helpers/api-error.ts`) is the only structured error type — throw it from controllers/DAOs with a
|
|
55
|
+
`StatusCodes` + `detail` message when a caller-facing failure occurs.
|
|
56
|
+
|
|
57
|
+
Controllers commonly call other controllers directly (not DAOs) when crossing resource boundaries (e.g.
|
|
58
|
+
`user-controller` calls `user-organization-controller` and `identity-token-controller`) — this keeps validation and
|
|
59
|
+
side effects (e.g. cache invalidation, notifications) consistent.
|
|
60
|
+
|
|
61
|
+
### Request scopes / route mounting (`src/v1/routes/v1-router.ts`)
|
|
62
|
+
|
|
63
|
+
- `/admin/*` — internal/back-office CRUD across nearly every resource (users, organizations, licenses, policies,
|
|
64
|
+
billing, schedules...). Gated by the `admin` middleware (`src/v1/middlewares/static/static-middleware.ts`),
|
|
65
|
+
which checks a static `x-admin-token` header against `SERVICES:AUTH:ADMIN`.
|
|
66
|
+
- `/auth/identity` + `/user` — end-user login/session flows, protected by identity bearer tokens
|
|
67
|
+
(`identity-token-middleware.ts`).
|
|
68
|
+
- `/auth/organization` + `/organization` — organization-scoped session flows (`organization-token-middleware.ts`),
|
|
69
|
+
which resolve and cache an `Environment` (user + organization) in Redis for 60s.
|
|
70
|
+
- `/service`, `/services` (public) — consumed by other Prow services. Authenticated via
|
|
71
|
+
`x-service-uuid`/`x-service-token` headers (`service-token-middleware.ts`), which also resolves and caches a
|
|
72
|
+
`ServiceEnvironment` (service + organization + licenses) in Redis for 60s, and can push license-change
|
|
73
|
+
notifications back to the calling service's configured webhook.
|
|
74
|
+
- `/billing` — Stripe/Holded-backed billing endpoints.
|
|
75
|
+
- `authority-middleware.ts` — used when *this* service's own actions need to check quota (`isPossible`) or license
|
|
76
|
+
status (`organization`) against itself via the `authority` client from `src/helpers/authority.ts`.
|
|
77
|
+
|
|
78
|
+
Auth middlewares consistently split into two steps chained in the router: an `exists` check (pull token out of
|
|
79
|
+
headers, 401 if missing) then a `validate` check (resolve token to a user/environment, attach to `req`). Downstream
|
|
80
|
+
middleware/controllers assume the previous step ran and throw a 500 "Middleware dependency broken" `APIError` if
|
|
81
|
+
the expected `req` property is missing — preserve this ordering when adding routes.
|
|
82
|
+
|
|
83
|
+
### Data layer
|
|
84
|
+
|
|
85
|
+
- MySQL is the primary store (`src/helpers/mysql.ts`, connection pool of 5). `src/v1/daos/schema.sql` is the
|
|
86
|
+
canonical schema reference (tables for users, organizations, memberships, services, licenses/limits, policies,
|
|
87
|
+
billing cycles, invoices, payment methods, accounting seats). DAOs use `mySql.queryConstructor` for simple
|
|
88
|
+
get/list/delete (built from a `Where<T>` object plus optional `Modifiers` — order/limit/timeout, see
|
|
89
|
+
`src/v1/common-types.ts`) and raw parameterized SQL strings for insert/update.
|
|
90
|
+
- JSON-typed DB columns (`properties`, `preferences`, `metadata`, `internal`, `utm`, `billing`) are
|
|
91
|
+
`JSON.stringify`'d going into DAOs and parsed back out in the controller's `parse()` step, which is also where
|
|
92
|
+
DB rows (`DBRecord`) get reshaped into the public-facing domain type (masking `password`, defaulting
|
|
93
|
+
`preferences.language`, etc).
|
|
94
|
+
- Redis (`src/v1/daos/redis-dao.ts`, generic `RedisDao<T>`) is used purely as a short-lived (60s) cache for
|
|
95
|
+
resolved auth environments, refreshed via `setImmediate` after the response is sent (stale-while-revalidate
|
|
96
|
+
style) rather than blocking the request.
|
|
97
|
+
|
|
98
|
+
### Scheduling & external integrations
|
|
99
|
+
|
|
100
|
+
- `src/v1/controllers/admin/schedule-controller.ts` registers cron-style jobs on construction (via
|
|
101
|
+
`Scheduler.schedule` from utility-functions) for license-limit checks, license-change fallback sync, billing
|
|
102
|
+
cycle rollover, and invoice generation — these run inside the service process, not as separate jobs.
|
|
103
|
+
- `src/helpers/stripe.ts` / `src/helpers/holded.ts` wrap the Stripe SDK and Holded invoicing API respectively.
|
|
104
|
+
- `src/helpers/notifications.ts` sends transactional email (Gmail templates) and Slack notifications through a
|
|
105
|
+
separate Prow notifications service; `NotificationEventNames` and their payload types are centralized in
|
|
106
|
+
`src/helpers/authority.ts`.
|
|
107
|
+
- `src/helpers/s3.ts` handles avatar/cover image upload for users/organizations/services.
|
|
108
|
+
|
|
109
|
+
### Deprecated code
|
|
110
|
+
|
|
111
|
+
Files/paths prefixed `@deprecated` (e.g. `controllers/auth/@deprecated oauth-controller.ts`,
|
|
112
|
+
`daos/@deprecated auth-dao.ts`) are kept for reference/rollback but are not part of the active auth flow — the
|
|
113
|
+
active identity/organization token flows are `identity-token-*` and `organization-token-*`. Don't extend the
|
|
114
|
+
deprecated files; treat new auth work as belonging in the token-based controllers/middlewares.
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Imagen de runtime para prow-authority (Node/Express).
|
|
2
|
+
#
|
|
3
|
+
# A diferencia de un front (que hornea sus vars en el bundle en build-time),
|
|
4
|
+
# esta API lee config/config.yaml en RUNTIME (ver src/helpers/config.ts,
|
|
5
|
+
# resuelve la ruta a <workdir>/config/config.yaml). Ese fichero contiene
|
|
6
|
+
# credenciales reales, esta en .gitignore y NUNCA debe copiarse dentro de
|
|
7
|
+
# la imagen. En su lugar se monta como volumen al arrancar el contenedor,
|
|
8
|
+
# apuntando al fichero real gestionado por prow-configuration-files:
|
|
9
|
+
#
|
|
10
|
+
# docker run -v /node/configuration-files/prow-authority.yaml:/app/config/config.yaml:ro ...
|
|
11
|
+
#
|
|
12
|
+
# El unico secret de build es NPM_TOKEN, necesario para instalar los
|
|
13
|
+
# paquetes privados @beseif-solutions/*.
|
|
14
|
+
#
|
|
15
|
+
# Build local:
|
|
16
|
+
# DOCKER_BUILDKIT=1 docker build \
|
|
17
|
+
# --secret id=npm_token,env=NPM_TOKEN \
|
|
18
|
+
# -t prow-authority:local .
|
|
19
|
+
#
|
|
20
|
+
# Run local (con un config.yaml de dev, ej. de prow-configuration-files):
|
|
21
|
+
# docker run -p 5001:5001 -v "$(pwd)/config.yaml:/app/config/config.yaml:ro" prow-authority:local
|
|
22
|
+
|
|
23
|
+
# ---- Stage 1: install deps + build ----------------------------------
|
|
24
|
+
FROM node:20.9.0-bookworm-slim AS builder
|
|
25
|
+
WORKDIR /app
|
|
26
|
+
|
|
27
|
+
COPY package.json package-lock.json ./
|
|
28
|
+
RUN --mount=type=secret,id=npm_token \
|
|
29
|
+
printf '//registry.npmjs.org/:_authToken=%s\n' "$(cat /run/secrets/npm_token)" > .npmrc \
|
|
30
|
+
&& npm ci \
|
|
31
|
+
&& rm -f .npmrc
|
|
32
|
+
|
|
33
|
+
COPY tsconfig.json ./
|
|
34
|
+
COPY src ./src
|
|
35
|
+
RUN npm run build
|
|
36
|
+
|
|
37
|
+
# ---- Stage 2: production deps only -----------------------------------
|
|
38
|
+
FROM node:20.9.0-bookworm-slim AS deps
|
|
39
|
+
WORKDIR /app
|
|
40
|
+
|
|
41
|
+
COPY package.json package-lock.json ./
|
|
42
|
+
RUN --mount=type=secret,id=npm_token \
|
|
43
|
+
printf '//registry.npmjs.org/:_authToken=%s\n' "$(cat /run/secrets/npm_token)" > .npmrc \
|
|
44
|
+
&& npm ci --omit=dev \
|
|
45
|
+
&& rm -f .npmrc
|
|
46
|
+
|
|
47
|
+
# ---- Stage 3: runtime --------------------------------------------------
|
|
48
|
+
FROM node:20.9.0-bookworm-slim AS runtime
|
|
49
|
+
WORKDIR /app
|
|
50
|
+
ENV NODE_ENV=production
|
|
51
|
+
|
|
52
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
53
|
+
COPY --from=builder /app/dist ./dist
|
|
54
|
+
COPY package.json ./
|
|
55
|
+
|
|
56
|
+
# config/config.yaml se monta como volumen (ver cabecera de este fichero).
|
|
57
|
+
# El directorio existe vacio en la imagen para que el mount tenga destino.
|
|
58
|
+
RUN mkdir -p /app/config
|
|
59
|
+
|
|
60
|
+
EXPOSE 5001
|
|
61
|
+
CMD ["node", "dist/index.js"]
|
package/Dockerfile.dev
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Imagen de desarrollo local (hot-reload). El codigo y config.yaml los
|
|
2
|
+
# proporciona docker-compose en tiempo de ejecucion (bind-mount + volumen con
|
|
3
|
+
# nombre para node_modules) — ver prow-configuration-files/docker-compose.yml,
|
|
4
|
+
# servicio `authority`. Esta imagen solo instala dependencias.
|
|
5
|
+
#
|
|
6
|
+
# El token de npm para @beseif-solutions/* entra como build secret y nunca
|
|
7
|
+
# queda grabado en la imagen.
|
|
8
|
+
|
|
9
|
+
FROM node:20.9.0-bookworm-slim
|
|
10
|
+
WORKDIR /app
|
|
11
|
+
|
|
12
|
+
COPY package.json package-lock.json ./
|
|
13
|
+
RUN --mount=type=secret,id=npm_token \
|
|
14
|
+
printf '//registry.npmjs.org/:_authToken=%s\n' "$(cat /run/secrets/npm_token)" > .npmrc \
|
|
15
|
+
&& npm ci \
|
|
16
|
+
&& rm -f .npmrc
|
|
17
|
+
|
|
18
|
+
# .npmrc de runtime: resuelve el token desde la variable NPM_TOKEN (inyectada
|
|
19
|
+
# por compose) para poder hacer `npm install <pkg>` dentro del contenedor ya
|
|
20
|
+
# arrancado. Vive en /root para que el bind-mount de /app no lo tape.
|
|
21
|
+
RUN printf '//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n' > /root/.npmrc
|
|
22
|
+
|
|
23
|
+
EXPOSE 5001
|
|
24
|
+
CMD ["npm", "run", "develop"]
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Demo provider for Zapdos-CLI documentation
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "prow-authority",
|
|
3
|
+
"version": "3.6.3",
|
|
4
|
+
"description": "Prow environment user management",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"clean": "rimraf dist",
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"build_watch": "tsc --watch",
|
|
9
|
+
"run": "nodemon './dist/index.js' --watch './dist/**/*.js'",
|
|
10
|
+
"develop": "npm-run-all clean build --parallel build_watch run",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"lint": "npx eslint -c .eslintrc.js --ext .ts src --fix"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [],
|
|
15
|
+
"author": "Beseif",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/beseif-solutions/prow-authority.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/beseif-solutions/prow-authority/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/beseif-solutions/prow-authority#readme",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@beseif-solutions/prow-authority-for-services": "1.1.0",
|
|
27
|
+
"@beseif-solutions/utility-functions": "1.0.3",
|
|
28
|
+
"@types/node": "^14.18.34",
|
|
29
|
+
"cors": "^2.8.5",
|
|
30
|
+
"country-list": "^2.4.1",
|
|
31
|
+
"country-list-spanish": "^0.3.2",
|
|
32
|
+
"express": "^4.17.1",
|
|
33
|
+
"form-data": "^4.0.2",
|
|
34
|
+
"http-status-codes": "^2.3.0",
|
|
35
|
+
"lodash": "^4.17.21",
|
|
36
|
+
"path": "^0.12.7",
|
|
37
|
+
"stripe": "^18.0.0",
|
|
38
|
+
"ts-node": "^8.10.2",
|
|
39
|
+
"typescript": "4.6.3"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@cpmech/httpcodes": "^3.18.0",
|
|
43
|
+
"@types/cors": "^2.8.17",
|
|
44
|
+
"@types/express": "^4.17.6",
|
|
45
|
+
"@types/lodash": "4.17.5",
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^5.38.0",
|
|
47
|
+
"@typescript-eslint/parser": "^5.38.0",
|
|
48
|
+
"eslint": "^8.24.0",
|
|
49
|
+
"eslint-plugin-jsdoc": "^39.3.6",
|
|
50
|
+
"eslint-plugin-prefer-arrow": "^1.2.3",
|
|
51
|
+
"husky": "^4.2.5",
|
|
52
|
+
"nodemon": "^2.0.4",
|
|
53
|
+
"npm-run-all": "^4.1.5",
|
|
54
|
+
"rimraf": "^3.0.2"
|
|
55
|
+
},
|
|
56
|
+
"husky": {
|
|
57
|
+
"hooks": {
|
|
58
|
+
"pre-push": "npm run build"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/App.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import cors from 'cors';
|
|
2
|
+
import express, { Request, Response } from 'express';
|
|
3
|
+
import httpcodes from 'http-status-codes';
|
|
4
|
+
import v1Router from './v1/routes/v1-router';
|
|
5
|
+
|
|
6
|
+
class App {
|
|
7
|
+
public express: express.Application;
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.express = express();
|
|
11
|
+
this.middleware();
|
|
12
|
+
this.routes();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private middleware(): void {
|
|
16
|
+
this.express.use(express.json({ limit: `200mb` }));
|
|
17
|
+
this.express.use(express.urlencoded({ extended: false }));
|
|
18
|
+
this.express.use(cors());
|
|
19
|
+
|
|
20
|
+
this.express.use((req, res, next) => {
|
|
21
|
+
console.log(`-> [${req.method}] ${req.originalUrl}`);
|
|
22
|
+
res.once(`finish`, () => {
|
|
23
|
+
console.log(`<- [${req.method}] ${req.originalUrl}`, res.statusCode);
|
|
24
|
+
});
|
|
25
|
+
next();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private readonly notFound = (req: Request, res: Response) => {
|
|
30
|
+
res.status(httpcodes.NOT_FOUND).json({ message: `Not found` });
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
private routes(): void {
|
|
34
|
+
this.express.use(`/v1`, v1Router);
|
|
35
|
+
this.express.use(this.notFound);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
export default new App().express;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
|
3
|
+
|
|
4
|
+
export default class APIError extends Error {
|
|
5
|
+
|
|
6
|
+
public readonly code: StatusCodes;
|
|
7
|
+
public readonly data: { code: StatusCodes, message: string, detail: string };
|
|
8
|
+
|
|
9
|
+
constructor(data: { code: StatusCodes, detail: string }) {
|
|
10
|
+
super(`API Error (${data.code}): ${data.detail}`);
|
|
11
|
+
this.code = data.code;
|
|
12
|
+
this.data = {
|
|
13
|
+
code: data.code,
|
|
14
|
+
message: getReasonPhrase(data.code),
|
|
15
|
+
detail: data.detail,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { StatusCodes } from 'http-status-codes';
|
|
21
|
+
|