Skip to content

Commit bfb7c6a

Browse files
committed
Merge branch 'proconnect-dev-env' into 2783-réparer-les-tests-e2e
2 parents e8a94f3 + b58c584 commit bfb7c6a

14 files changed

Lines changed: 550 additions & 203 deletions

File tree

.github/workflows/review-auto.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,43 @@ jobs:
157157
buildkit-svc-count: ${{ vars.BUILDKIT_SVC_COUNT }}
158158
buildkit-daemon-address: ${{ vars.BUILDKIT_DAEMON_ADDRESS }}
159159

160+
build-keycloak:
161+
environment: build-review-auto
162+
outputs:
163+
tags: ${{ steps.meta.outputs.tags }}
164+
runs-on: ubuntu-latest
165+
steps:
166+
- name: ⏬ Checkout code repository
167+
uses: actions/checkout@v4
168+
169+
- name: 📌 Extract metadata (tags, labels) for Docker
170+
id: meta
171+
uses: docker/metadata-action@v5
172+
with:
173+
images: ${{ vars.REGISTRY_URL }}/${{ vars.PROJECT_NAME }}/${{ github.event.repository.name }}/keycloak
174+
tags: |
175+
type=sha,prefix=persist-,format=long,enable=${{
176+
github.ref_name != 'prod'
177+
}},priority=840
178+
type=sha,prefix=sha-,format=long,priority=890
179+
type=ref,event=branch,priority=600
180+
181+
- name: 📦 Build and push Docker image for keycloak
182+
uses: socialgouv/workflows/actions/buildkit@v1
183+
with:
184+
context: "."
185+
dockerfile: "packages/keycloak/Dockerfile"
186+
tags: ${{ steps.meta.outputs.tags }}
187+
labels: ${{ steps.meta.outputs.labels }}
188+
registry: "${{ vars.REGISTRY_URL }}"
189+
registry-username: "${{ secrets.REGISTRY_USERNAME }}"
190+
registry-password: "${{ secrets.REGISTRY_PASSWORD }}"
191+
buildkit-cert-ca: "${{ secrets.BUILDKIT_CERT_CA }}"
192+
buildkit-cert: "${{ secrets.BUILDKIT_CERT }}"
193+
buildkit-cert-key: "${{ secrets.BUILDKIT_CERT_KEY }}"
194+
buildkit-svc-count: ${{ vars.BUILDKIT_SVC_COUNT }}
195+
buildkit-daemon-address: ${{ vars.BUILDKIT_DAEMON_ADDRESS }}
196+
160197
kontinuous:
161198
needs: [build-app, build-nginx, build-files]
162199
name: "Deploy on Kubernetes 🐳"

.github/workflows/review.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,43 @@ jobs:
159159
buildkit-svc-count: ${{ vars.BUILDKIT_SVC_COUNT }}
160160
buildkit-daemon-address: ${{ vars.BUILDKIT_DAEMON_ADDRESS }}
161161

162+
build-keycloak:
163+
environment: build-review
164+
outputs:
165+
tags: ${{ steps.meta.outputs.tags }}
166+
runs-on: ubuntu-latest
167+
steps:
168+
- name: ⏬ Checkout code repository
169+
uses: actions/checkout@v4
170+
171+
- name: 📌 Extract metadata (tags, labels) for Docker
172+
id: meta
173+
uses: docker/metadata-action@v5
174+
with:
175+
images: ${{ vars.REGISTRY_URL }}/${{ vars.PROJECT_NAME }}/${{ github.event.repository.name }}/keycloak
176+
tags: |
177+
type=sha,prefix=persist-,format=long,enable=${{
178+
github.ref_name != 'prod'
179+
}},priority=840
180+
type=sha,prefix=sha-,format=long,priority=890
181+
type=ref,event=branch,priority=600
182+
183+
- name: 📦 Build and push Docker image for keycloak
184+
uses: socialgouv/workflows/actions/buildkit@v1
185+
with:
186+
context: "."
187+
dockerfile: "packages/keycloak/Dockerfile"
188+
tags: ${{ steps.meta.outputs.tags }}
189+
labels: ${{ steps.meta.outputs.labels }}
190+
registry: "${{ vars.REGISTRY_URL }}"
191+
registry-username: "${{ secrets.REGISTRY_USERNAME }}"
192+
registry-password: "${{ secrets.REGISTRY_PASSWORD }}"
193+
buildkit-cert-ca: "${{ secrets.BUILDKIT_CERT_CA }}"
194+
buildkit-cert: "${{ secrets.BUILDKIT_CERT }}"
195+
buildkit-cert-key: "${{ secrets.BUILDKIT_CERT_KEY }}"
196+
buildkit-svc-count: ${{ vars.BUILDKIT_SVC_COUNT }}
197+
buildkit-daemon-address: ${{ vars.BUILDKIT_DAEMON_ADDRESS }}
198+
162199
kontinuous:
163200
needs: [build-app, build-nginx, build-files]
164201
name: "Deploy on Kubernetes 🐳"

.kontinuous/env/dev/templates/proconnect.configmap.yaml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ apiVersion: v1
33
metadata:
44
name: proconnect
55
data:
6-
NEXT_PUBLIC_PROCONNECT_CALLBACK_URL: https://app.proconnect.gouv.fr
7-
PROCONNECT_AUTHORIZATION_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/authorize
8-
PROCONNECT_TOKEN_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/token
9-
PROCONNECT_USERINFO_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/userinfo
10-
PROCONNECT_SCOPE: "openid email given_name usual_name phone_number organizations"
6+
SECURITY_PROCONNECT_CLIENT_ID: "egapro-dev"
7+
SECURITY_PROCONNECT_CLIENT_SECRET: "dev-secret-key-for-local-development-only"
8+
EGAPRO_PROCONNECT_AUTHORIZATION_ENDPOINT: "https://keycloak-egapro-{{ .Values.global.branchSlug }}.ovh.fabrique.social.gouv.fr/realms/atlas/protocol/openid-connect/auth"
9+
EGAPRO_PROCONNECT_TOKEN_ENDPOINT: "http://keycloak/realms/atlas/protocol/openid-connect/token"
10+
EGAPRO_PROCONNECT_USERINFO_ENDPOINT: "http://keycloak/realms/atlas/protocol/openid-connect/userinfo"
11+
EGAPRO_PROCONNECT_ISSUER: "https://keycloak-egapro-{{ .Values.global.branchSlug }}.ovh.fabrique.social.gouv.fr"
12+
NEXT_PUBLIC_PROCONNECT_CALLBACK_URL: "https://egapro-{{ .Values.global.branchSlug }}.ovh.fabrique.social.gouv.fr/api/auth/callback/proconnect"
13+
EGAPRO_ORIGIN: "https://egapro-{{ .Values.global.branchSlug }}.ovh.fabrique.social.gouv.fr"

.kontinuous/env/dev/templates/proconnect.sealed-secret.yaml

Lines changed: 0 additions & 16 deletions
This file was deleted.

.kontinuous/env/dev/values.yaml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,40 @@ app:
22
annotations:
33
oblik.socialgouv.io/min-limit-cpu: 2
44
envFrom:
5+
- configMapRef:
6+
name: "proconnect"
7+
optional: true
58
- secretRef:
69
name: "proconnect"
10+
optional: true
711
vars:
812
MAILER_ENABLE: "True"
913
MAILER_SMTP_HOST: maildev
1014
MAILER_SMTP_PORT: "1025"
1115
MAILER_SMTP_SSL: "False"
12-
EGAPRO_PROCONNECT_DISCOVERY_URL: "https://fca.integ01.dev-agentconnect.fr/api/v2"
16+
# Note: EGAPRO_PROCONNECT_ISSUER sera surchargé par le ConfigMap avec l'URL externe
17+
# L'URL interne est gardée ici comme fallback pour les tests locaux
18+
EGAPRO_PROCONNECT_DISCOVERY_URL: "http://localhost:8080/realms/atlas/.well-known/openid-configuration"
19+
20+
keycloak:
21+
~chart: app
22+
imagePackage: keycloak
23+
containerPort: 8080
24+
probesPath: /
25+
resources:
26+
requests:
27+
cpu: 200m
28+
memory: 512Mi
29+
limits:
30+
cpu: 1000m
31+
memory: 1Gi
32+
vars:
33+
KEYCLOAK_ADMIN: admin
34+
KEYCLOAK_ADMIN_PASSWORD: admin
35+
KEYCLOAK_CLIENT_SECRET: dev-secret-key-for-local-development-only
36+
KC_PROXY: edge
37+
KC_HOSTNAME_STRICT: "false"
38+
EGAPRO_ORIGIN: "https://egapro-{{ .Values.global.branchSlug }}.ovh.fabrique.social.gouv.fr"
1339

1440
maildev: {}
1541
pgweb: {}
@@ -21,4 +47,3 @@ nginx:
2147
annotations:
2248
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
2349
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
24-

docker-compose.yml

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,6 @@
11
version: "3.8"
22

33
services:
4-
files:
5-
image: egapro_files
6-
build:
7-
context: .
8-
dockerfile: packages/files/Dockerfile
9-
volumes:
10-
- ./packages/files:/app
11-
- ./mnt:/mnt
12-
environment:
13-
ROOT_PATH: /mnt
14-
TRUSTED_PROXY_IP: "172.17.0.1"
15-
AUTH_PASSWD_FILE: "/app/.htpasswd"
16-
WHITELIST_IP: "127.0.0.1"
17-
FILES_PUBLIC: "dgt.xlsx,dgt-representation.xlsx,index-egalite-fh.xlsx,indexes.csv,full.ndjson,dgt-export-representation.xlsx"
18-
FILES_RESTRICTED: ""
19-
ports:
20-
- 8080:8080
21-
restart: always
22-
23-
244
maildev:
255
image: djfarrelly/maildev
266
command: bin/maildev --hide-extensions STARTTLS
@@ -72,17 +52,17 @@ services:
7252
- pgadmin:/var/lib/pgadmin
7353

7454
keycloak:
75-
image: quay.io/keycloak/keycloak:21.1.1
76-
command: start-dev --import-realm
77-
ports:
78-
- 8081:8080
55+
build:
56+
context: .
57+
dockerfile: ./packages/keycloak/Dockerfile
7958
environment:
8059
KEYCLOAK_ADMIN: admin
8160
KEYCLOAK_ADMIN_PASSWORD: admin
82-
KEYCLOAK_CLIENT_SECRET: dev-secret-key-for-local-development-only
61+
EGAPRO_ORIGIN: "http://localhost:3000"
62+
ports:
63+
- "8081:8080"
8364
volumes:
84-
- ./keycloak/realm-export.json:/opt/keycloak/data/import/realm-export.json
85-
restart: always
65+
- ./packages/keycloak/realm-export.json:/opt/keycloak/data/import/realm-export.json:ro
8666

8767
# TODO handle redirect with nginx (https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/#configuring-for-use-with-the-nginx-auth_request-directive)
8868
oauth2-proxy-github:

packages/app/src/api/core-domain/infra/auth/ProConnectProvider.ts

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ export interface ProConnectProfile {
3030
exp?: number;
3131
}
3232

33-
34-
3533
export function ProConnectProvider<P extends ProConnectProfile>(
3634
options?: OAuthUserConfig<P>,
3735
): OAuthConfig<P> {
@@ -56,70 +54,91 @@ export function ProConnectProvider<P extends ProConnectProfile>(
5654
userinfo: {
5755
url: proconnect.userinfo_endpoint,
5856
async request({ tokens }) {
59-
if (!tokens.access_token) throw new Error("No access token");
57+
if (!tokens.access_token) {
58+
logger.error("❌ Pas d'access_token disponible pour userinfo");
59+
throw new Error("No access token");
60+
}
6061

61-
const response = await fetch(proconnect.userinfo_endpoint, {
62-
headers: {
63-
Authorization: `Bearer ${tokens.access_token}`,
64-
Accept: "application/json",
65-
},
66-
cache: "no-store",
67-
});
62+
try {
63+
const response = await fetch(proconnect.userinfo_endpoint, {
64+
headers: {
65+
Authorization: `Bearer ${tokens.access_token}`,
66+
Accept: "application/json",
67+
},
68+
cache: "no-store",
69+
});
6870

69-
if (!response.ok) {
70-
const text = await response.text();
71-
logger.error(
72-
{ status: response.status, body: text },
73-
"ProConnect userinfo error",
74-
);
75-
throw new Error(`ProConnect userinfo failed: ${response.status}`);
76-
}
71+
if (!response.ok) {
72+
const text = await response.text();
73+
logger.error(
74+
{
75+
status: response.status,
76+
statusText: response.statusText,
77+
url: proconnect.userinfo_endpoint,
78+
body: text,
79+
},
80+
"❌ Userinfo request failed",
81+
);
82+
throw new Error(`ProConnect userinfo failed: ${response.status}`);
83+
}
7784

78-
const contentType = response.headers.get("content-type") || "";
79-
const rawBody = await response.text();
85+
const contentType = response.headers.get("content-type") || "";
86+
const rawBody = await response.text();
8087

81-
if (contentType.includes("jwt") || rawBody.startsWith("ey")) {
82-
const parts = rawBody.trim().split(".");
83-
if (parts.length !== 3) throw new Error("Invalid JWT format");
88+
if (contentType.includes("jwt") || rawBody.startsWith("ey")) {
89+
const parts = rawBody.trim().split(".");
90+
if (parts.length !== 3) throw new Error("Invalid JWT format");
8491

85-
let payload = parts[1];
86-
payload += "=".repeat((4 - (payload.length % 4)) % 4);
87-
const decoded = JSON.parse(
88-
Buffer.from(payload, "base64url").toString("utf-8"),
92+
let payload = parts[1];
93+
payload += "=".repeat((4 - (payload.length % 4)) % 4);
94+
const decoded = JSON.parse(
95+
Buffer.from(payload, "base64url").toString("utf-8"),
96+
);
97+
return decoded;
98+
}
99+
100+
return JSON.parse(rawBody);
101+
} catch (error) {
102+
logger.error(
103+
{
104+
error: error instanceof Error ? error.message : String(error),
105+
url: proconnect.userinfo_endpoint,
106+
},
107+
"❌ Erreur lors de la requête userinfo",
89108
);
90-
return decoded;
109+
throw error;
91110
}
92-
93-
return JSON.parse(rawBody);
94111
},
95112
},
96113
checks: ["pkce", "state"],
97114
async profile(profile: ProConnectProfile) {
98-
logger.info({ profile }, "ProConnect profile reçu");
99-
100115
return {
101116
id: profile.sub,
102117
email: profile.email,
103118
emailVerified: profile.email_verified ?? false,
104-
name: `${profile.given_name ?? ""} ${profile.usual_name ?? ""}`.trim() || null,
119+
name:
120+
`${profile.given_name ?? ""} ${profile.usual_name ?? ""}`.trim() ||
121+
null,
105122
given_name: profile.given_name ?? null,
106123
family_name: profile.usual_name ?? null,
107124
phone_number: profile.phone_number
108125
? profile.phone_number.replace(/[.\-\s]/g, "")
109126
: null,
110127
siret: profile.siret || null,
111-
organization: profile.siret ? {
112-
id: parseInt(profile.siret, 10),
113-
label: null,
114-
siren: profile.siret.substring(0, 9),
115-
siret: profile.siret,
116-
is_collectivite_territoriale: false,
117-
is_external: false,
118-
is_service_public: false,
119-
} : undefined,
128+
organization: profile.siret
129+
? {
130+
id: parseInt(profile.siret, 10),
131+
label: null,
132+
siren: profile.siret.substring(0, 9),
133+
siret: profile.siret,
134+
is_collectivite_territoriale: false,
135+
is_external: false,
136+
is_service_public: false,
137+
}
138+
: undefined,
120139
raw: profile,
121140
};
122141
},
123142
...options,
124143
};
125-
}
144+
}

0 commit comments

Comments
 (0)