IA align
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 50s
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 50s
This commit is contained in:
parent
02820b2862
commit
efe9dbde1b
48
.env.backup
Normal file
48
.env.backup
Normal file
@ -0,0 +1,48 @@
|
||||
# Configuration OVH
|
||||
OVH_APP_KEY=5ab0709bbb65ef26
|
||||
OVH_APP_SECRET=de1fac1779d707d263a611a557cd5766
|
||||
OVH_CONSUMER_KEY=5fe817829b8a9c780cfa2354f8312ece
|
||||
OVH_SMS_SERVICE_NAME=sms-tt802880-1
|
||||
|
||||
# Configuration SMS Factor
|
||||
SMS_FACTOR_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI4NzgzNiIsImlhdCI6MTcwMTMzOTY1Mi45NDUzOH0.GNoqLb5MDBWuniNlQjbr1PKolwxGqBZe_tf4IMObvHw
|
||||
|
||||
#Configuration Mailchimp
|
||||
MAILCHIMP_API_KEY=md-VVfaml-ApIV4nsGgaJKl0A
|
||||
MAILCHIMP_KEY=3fa54304bc766dfd0b8043a827b28a3a-us17
|
||||
MAILCHIMP_LIST_ID=a48d9ad852
|
||||
|
||||
#Configuration Stripe
|
||||
STRIPE_SECRET_KEY=sk_test_51OwKmMP5xh1u9BqSeFpqw0Yr15hHtFsh0pvRGaE0VERhlYtvw33ND1qiGA6Dy1DPmmV61B6BqIimlhuv7bwElhjF00PLQwD60n
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID=price_1P66fuP5xh1u9BqSHj0O6Uy3
|
||||
STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID=price_1P9NsRP5xh1u9BqSFgkUDbQY
|
||||
STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID=price_1P66RqP5xh1u9BqSuUzkQNac
|
||||
STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID=price_1P9NpKP5xh1u9BqSApFogvUB
|
||||
|
||||
# Configuration serveur
|
||||
APP_HOST=https://demo.4nkweb.com
|
||||
PORT=
|
||||
|
||||
# Configuration front-end
|
||||
NEXT_PUBLIC_4NK_URL=https://demo.4nkweb.com
|
||||
NEXT_PUBLIC_FRONT_APP_HOST=https://demo.4nkweb.com
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL=https://qual-connexion.idnot.fr
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT=/IdPOAuth2/authorize/idnot_idp_v1
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID=4501646203F3EF67
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL=http
|
||||
NEXT_PUBLIC_BACK_API_HOST=localhost
|
||||
BACK_API_PORT=8081
|
||||
BACK_API_ROOT_URL=/api
|
||||
BACK_API_VERSION=/v1
|
||||
|
||||
# Configuration idnot
|
||||
IDNOT_ANNUARY_BASE_URL='https://qual-api.notaires.fr/annuaire'
|
||||
IDNOT_API_KEY='ba557f84-0bf6-4dbf-844f-df2767555e3e'
|
||||
|
||||
# Configuration PostgreSQL
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
58
.env.backup2
Normal file
58
.env.backup2
Normal file
@ -0,0 +1,58 @@
|
||||
# Configuration OVH
|
||||
OVH_APP_KEY=5ab0709bbb65ef26
|
||||
OVH_APP_SECRET=de1fac1779d707d263a611a557cd5766
|
||||
OVH_CONSUMER_KEY=5fe817829b8a9c780cfa2354f8312ece
|
||||
OVH_SMS_SERVICE_NAME=sms-tt802880-1
|
||||
|
||||
# Configuration SMS Factor
|
||||
SMS_FACTOR_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI4NzgzNiIsImlhdCI6MTcwMTMzOTY1Mi45NDUzOH0.GNoqLb5MDBWuniNlQjbr1PKolwxGqBZe_tf4IMObvHw
|
||||
|
||||
#Configuration Mailchimp
|
||||
MAILCHIMP_API_KEY=md-VVfaml-ApIV4nsGgaJKl0A
|
||||
MAILCHIMP_KEY=3fa54304bc766dfd0b8043a827b28a3a-us17
|
||||
MAILCHIMP_LIST_ID=a48d9ad852
|
||||
|
||||
#Configuration Stripe
|
||||
STRIPE_SECRET_KEY=sk_test_51OwKmMP5xh1u9BqSeFpqw0Yr15hHtFsh0pvRGaE0VERhlYtvw33ND1qiGA6Dy1DPmmV61B6BqIimlhuv7bwElhjF00PLQwD60n
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID=price_1P66fuP5xh1u9BqSHj0O6Uy3
|
||||
STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID=price_1P9NsRP5xh1u9BqSFgkUDbQY
|
||||
STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID=price_1P66RqP5xh1u9BqSuUzkQNac
|
||||
STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID=price_1P9NpKP5xh1u9BqSApFogvUB
|
||||
|
||||
# Configuration serveur
|
||||
APP_HOST=https://demo.4nkweb.com
|
||||
PORT=
|
||||
|
||||
# Configuration front-end
|
||||
NEXT_PUBLIC_4NK_URL=https://demo.4nkweb.com
|
||||
NEXT_PUBLIC_FRONT_APP_HOST=https://demo.4nkweb.com
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL=https://qual-connexion.idnot.fr
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT=/IdPOAuth2/authorize/idnot_idp_v1
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID=4501646203F3EF67
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL=http
|
||||
NEXT_PUBLIC_BACK_API_HOST=localhost
|
||||
BACK_API_PORT=8081
|
||||
BACK_API_ROOT_URL=/api
|
||||
BACK_API_VERSION=/v1
|
||||
|
||||
# Configuration idnot
|
||||
IDNOT_ANNUARY_BASE_URL='https://qual-api.notaires.fr/annuaire'
|
||||
IDNOT_API_KEY='ba557f84-0bf6-4dbf-844f-df2767555e3e'
|
||||
|
||||
# Configuration PostgreSQL
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
|
||||
# Variables IdNot manquantes pour l'authentification
|
||||
IDNOT_API_BASE_URL='https://qual-api.notaires.fr'
|
||||
IDNOT_REDIRECT_URI='https://dev4.4nkweb.com/lecoffre/authorized-client'
|
||||
IDNOT_TOKEN_URL='https://qual-connexion.idnot.fr/IdPOAuth2/token/idnot_idp_v1'
|
||||
|
||||
IDNOT_API_KEY=ba557f84-0bf6-4dbf-844f-df2767555e3e
|
||||
IDNOT_CLIENT_ID=B3CE56353EDB15A9
|
||||
IDNOT_CLIENT_SECRET=3F733549E879878344B6C949B366BB5CDBB2DB5B7F7AB7EBBEBB0F0DD0776D1C
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID=B3CE56353EDB15A9
|
57
.env.bak
Normal file
57
.env.bak
Normal file
@ -0,0 +1,57 @@
|
||||
# Configuration OVH
|
||||
OVH_APP_KEY=5ab0709bbb65ef26
|
||||
OVH_APP_SECRET=de1fac1779d707d263a611a557cd5766
|
||||
OVH_CONSUMER_KEY=5fe817829b8a9c780cfa2354f8312ece
|
||||
OVH_SMS_SERVICE_NAME=sms-tt802880-1
|
||||
|
||||
# Configuration SMS Factor
|
||||
SMS_FACTOR_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI4NzgzNiIsImlhdCI6MTcwMTMzOTY1Mi45NDUzOH0.GNoqLb5MDBWuniNlQjbr1PKolwxGqBZe_tf4IMObvHw
|
||||
|
||||
#Configuration Mailchimp
|
||||
MAILCHIMP_API_KEY=md-VVfaml-ApIV4nsGgaJKl0A
|
||||
MAILCHIMP_KEY=3fa54304bc766dfd0b8043a827b28a3a-us17
|
||||
MAILCHIMP_LIST_ID=a48d9ad852
|
||||
|
||||
#Configuration Stripe
|
||||
STRIPE_SECRET_KEY=sk_test_51OwKmMP5xh1u9BqSeFpqw0Yr15hHtFsh0pvRGaE0VERhlYtvw33ND1qiGA6Dy1DPmmV61B6BqIimlhuv7bwElhjF00PLQwD60n
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID=price_1P66fuP5xh1u9BqSHj0O6Uy3
|
||||
STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID=price_1P9NsRP5xh1u9BqSFgkUDbQY
|
||||
STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID=price_1P66RqP5xh1u9BqSuUzkQNac
|
||||
STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID=price_1P9NpKP5xh1u9BqSApFogvUB
|
||||
|
||||
# Configuration serveur
|
||||
APP_HOST=https://demo.4nkweb.com
|
||||
PORT=
|
||||
|
||||
# Configuration front-end
|
||||
NEXT_PUBLIC_4NK_URL=https://demo.4nkweb.com
|
||||
NEXT_PUBLIC_FRONT_APP_HOST=https://demo.4nkweb.com
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL=https://qual-connexion.idnot.fr
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT=/IdPOAuth2/authorize/idnot_idp_v1
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL=http
|
||||
NEXT_PUBLIC_BACK_API_HOST=localhost
|
||||
BACK_API_PORT=8081
|
||||
BACK_API_ROOT_URL=/api
|
||||
BACK_API_VERSION=/v1
|
||||
|
||||
# Configuration idnot
|
||||
IDNOT_ANNUARY_BASE_URL='https://qual-api.notaires.fr/annuaire'
|
||||
IDNOT_API_KEY='ba557f84-0bf6-4dbf-844f-df2767555e3e'
|
||||
|
||||
# Configuration PostgreSQL
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
|
||||
# Variables IdNot manquantes pour l'authentification
|
||||
IDNOT_API_BASE_URL='https://qual-api.notaires.fr'
|
||||
IDNOT_REDIRECT_URI='https://lecoffreio.4nkweb.com/authorized-client'
|
||||
IDNOT_TOKEN_URL='https://qual-connexion.idnot.fr/user/IdPOAuth2/token/idnot_idp_v1'
|
||||
|
||||
IDNOT_API_KEY=ba557f84-0bf6-4dbf-844f-df2767555e3e
|
||||
IDNOT_CLIENT_ID=B3CE56353EDB15A9
|
||||
IDNOT_CLIENT_SECRET=3F733549E879878344B6C949B366BB5CDBB2DB5B7F7AB7EBBEBB0F0DD0776D1C
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID=B3CE56353EDB15A9
|
63
.env.bak.1758708401
Normal file
63
.env.bak.1758708401
Normal file
@ -0,0 +1,63 @@
|
||||
# Configuration OVH
|
||||
OVH_APP_KEY=5ab0709bbb65ef26
|
||||
OVH_APP_SECRET=de1fac1779d707d263a611a557cd5766
|
||||
OVH_CONSUMER_KEY=5fe817829b8a9c780cfa2354f8312ece
|
||||
OVH_SMS_SERVICE_NAME=sms-tt802880-1
|
||||
|
||||
# Configuration SMS Factor
|
||||
SMS_FACTOR_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI4NzgzNiIsImlhdCI6MTcwMTMzOTY1Mi45NDUzOH0.GNoqLb5MDBWuniNlQjbr1PKolwxGqBZe_tf4IMObvHw
|
||||
|
||||
#Configuration Mailchimp
|
||||
MAILCHIMP_API_KEY=md-VVfaml-ApIV4nsGgaJKl0A
|
||||
MAILCHIMP_KEY=3fa54304bc766dfd0b8043a827b28a3a-us17
|
||||
MAILCHIMP_LIST_ID=a48d9ad852
|
||||
|
||||
#Configuration Stripe
|
||||
STRIPE_SECRET_KEY=sk_test_51OwKmMP5xh1u9BqSeFpqw0Yr15hHtFsh0pvRGaE0VERhlYtvw33ND1qiGA6Dy1DPmmV61B6BqIimlhuv7bwElhjF00PLQwD60n
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID=price_1P66fuP5xh1u9BqSHj0O6Uy3
|
||||
STRIPE_STANDARD_ANNUAL_SUBSCRIPTION_PRICE_ID=price_1P9NsRP5xh1u9BqSFgkUDbQY
|
||||
STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID=price_1P66RqP5xh1u9BqSuUzkQNac
|
||||
STRIPE_UNLIMITED_ANNUAL_SUBSCRIPTION_PRICE_ID=price_1P9NpKP5xh1u9BqSApFogvUB
|
||||
|
||||
# Configuration serveur
|
||||
APP_HOST=https://demo.4nkweb.com
|
||||
PORT=
|
||||
|
||||
# Configuration front-end
|
||||
NEXT_PUBLIC_4NK_URL=https://demo.4nkweb.com
|
||||
NEXT_PUBLIC_FRONT_APP_HOST=https://demo.4nkweb.com
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL=https://qual-connexion.idnot.fr
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT=/IdPOAuth2/authorize/idnot_idp_v1
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL=http
|
||||
NEXT_PUBLIC_BACK_API_HOST=localhost
|
||||
BACK_API_PORT=8081
|
||||
BACK_API_ROOT_URL=/api
|
||||
BACK_API_VERSION=/v1
|
||||
|
||||
# Configuration idnot
|
||||
IDNOT_ANNUARY_BASE_URL='https://qual-api.notaires.fr/annuaire'
|
||||
IDNOT_API_KEY='ba557f84-0bf6-4dbf-844f-df2767555e3e'
|
||||
|
||||
# Configuration PostgreSQL
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
|
||||
# Variables IdNot manquantes pour l'authentification
|
||||
IDNOT_API_BASE_URL='https://qual-api.notaires.fr'
|
||||
IDNOT_REDIRECT_URI='https://lecoffreio.4nkweb.com/authorized-client'
|
||||
IDNOT_TOKEN_URL='https://qual-connexion.idnot.fr/user/IdPOAuth2/token/idnot_idp_v1'
|
||||
|
||||
IDNOT_API_KEY=ba557f84-0bf6-4dbf-844f-df2767555e3e
|
||||
IDNOT_CLIENT_ID=B3CE56353EDB15A9
|
||||
IDNOT_CLIENT_SECRET=3F733549E879878344B6C949B366BB5CDBB2DB5B7F7AB7EBBEBB0F0DD0776D1C
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID=B3CE56353EDB15A9
|
||||
STATE_TTL_SECONDS=180
|
||||
ALLOW_LOCALHOST_REDIRECTS=true
|
||||
ALLOWED_REDIRECT_HOST_PATTERNS=^lecoffreio.4nkweb.com$
|
||||
|
||||
BACK_HMAC_SECRET=7e0f4a8b7c9d3e2fb6c1a5d4e8f09b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70
|
||||
|
@ -1,7 +1,38 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name dev3.4nkweb.com;
|
||||
|
||||
# Callback IdNot -> backend, avec CORS dynamique et masquage des en-têtes upstream
|
||||
location = /idnot/callback {
|
||||
# Masquer les en-têtes CORS envoyés par l'upstream (Express)
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_hide_header Access-Control-Allow-Credentials;
|
||||
proxy_hide_header Access-Control-Allow-Headers;
|
||||
proxy_hide_header Access-Control-Allow-Methods;
|
||||
|
||||
# CORS dynamique: autorise dev4, lecoffreio.4nkweb.com, localhost:3000 et sous-domaines *.4nkweb.com
|
||||
set $cors_origin "";
|
||||
if ($http_origin ~* ^(https://dev4\.4nkweb\.com|http://local\.4nkweb\.com:3000|http://localhost:3000|https://.*\.4nkweb\.com)$) {
|
||||
set $cors_origin $http_origin;
|
||||
}
|
||||
|
||||
# Préflight OPTIONS
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, x-session-id" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" always;
|
||||
return 204;
|
||||
}
|
||||
|
||||
# En-têtes CORS pour les autres méthodes
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, x-session-id" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" always;
|
||||
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@ -35,7 +66,7 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
|
||||
proxy_read_timeout 86400;
|
||||
proxy_read_timeout 86400;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
}
|
||||
|
||||
@ -63,6 +94,35 @@ server {
|
||||
}
|
||||
|
||||
location ^~ /api/ {
|
||||
# Masquer les en-têtes CORS de l'upstream (Express)
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_hide_header Access-Control-Allow-Credentials;
|
||||
proxy_hide_header Access-Control-Allow-Headers;
|
||||
proxy_hide_header Access-Control-Allow-Methods;
|
||||
|
||||
# CORS dynamique: autorise dev4, lecoffreio.4nkweb.com, localhost:3000 et sous-domaines *.4nkweb.com
|
||||
set $cors_origin "";
|
||||
if ($http_origin ~* ^(https://dev4\.4nkweb\.com|http://local\.4nkweb\.com:3000|http://localhost:3000|https://.*\.4nkweb\.com)$) {
|
||||
set $cors_origin $http_origin;
|
||||
}
|
||||
|
||||
# Préflight OPTIONS
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, x-session-id" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" always;
|
||||
return 204;
|
||||
}
|
||||
|
||||
# En-têtes CORS pour les autres méthodes
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, x-session-id" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" always;
|
||||
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@ -100,5 +160,3 @@ server {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
176
confs/nginx/dev3.4nkweb.com.fixed.conf
Normal file
176
confs/nginx/dev3.4nkweb.com.fixed.conf
Normal file
@ -0,0 +1,176 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name dev3.4nkweb.com;
|
||||
location = /idnot/callback {
|
||||
# Hide upstream CORS headers
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_hide_header Access-Control-Allow-Credentials;
|
||||
proxy_hide_header Access-Control-Allow-Headers;
|
||||
proxy_hide_header Access-Control-Allow-Methods;
|
||||
|
||||
# Dynamic CORS allowlist
|
||||
set $cors_origin "";
|
||||
if ($http_origin ~* ^(https://dev4\.4nkweb\.com|http://local\.(4nkweb|4nkdev)\.com:3000|http://localhost:3000|https://.*\.4nkweb\.com)$) {
|
||||
set $cors_origin $http_origin;
|
||||
}
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, x-session-id" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" always;
|
||||
return 204;
|
||||
}
|
||||
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, x-session-id" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" always;
|
||||
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/dev3.4nkweb.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/dev3.4nkweb.com/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
# Redirection des requêtes HTTP normales vers Vite
|
||||
location / {
|
||||
proxy_pass http://localhost:3004;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /ws/ {
|
||||
proxy_pass http://localhost:8090;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
|
||||
proxy_read_timeout 86400;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
}
|
||||
|
||||
location /storage/ {
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
|
||||
add_header 'Access-Control-Max-Age' 86400;
|
||||
add_header 'Content-Length' 0;
|
||||
add_header 'Content-Type' 'text/plain';
|
||||
return 204;
|
||||
}
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
|
||||
|
||||
rewrite ^/storage(/.*)$ $1 break;
|
||||
proxy_pass http://localhost:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location ^~ /api/ {
|
||||
# Hide upstream CORS headers
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_hide_header Access-Control-Allow-Credentials;
|
||||
proxy_hide_header Access-Control-Allow-Headers;
|
||||
proxy_hide_header Access-Control-Allow-Methods;
|
||||
|
||||
# Dynamic CORS allowlist
|
||||
set $cors_origin "";
|
||||
if ($http_origin ~* ^(https://dev4\.4nkweb\.com|http://local\.(4nkweb|4nkdev)\.com:3000|http://localhost:3000|https://.*\.4nkweb\.com)$) {
|
||||
set $cors_origin $http_origin;
|
||||
}
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, x-session-id" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" always;
|
||||
return 204;
|
||||
}
|
||||
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization, x-session-id" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" always;
|
||||
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location @handle_502 {
|
||||
internal;
|
||||
add_header Access-Control-Allow-Origin "*" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Accept, X-Requested-With" always;
|
||||
return 502;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = dev3.4nkweb.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name dev3.4nkweb.com;
|
||||
location = /idnot/callback {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
|
||||
|
||||
}
|
||||
server {
|
||||
if ($host = dev3.4nkweb.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name dev3.4nkweb.com;
|
||||
location = /idnot/callback {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
|
||||
|
||||
}
|
45
confs/nginx/dev3.4nkweb.com.fixed.conf.b64
Normal file
45
confs/nginx/dev3.4nkweb.com.fixed.conf.b64
Normal file
@ -0,0 +1,45 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name dev3.4nkweb.com;
|
||||
location = /idnot/callback {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/dev3.4nkweb.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/dev3.4nkweb.com/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
# Redirection des requêtes HTTP normales vers Vite
|
||||
location / {
|
||||
proxy_pass http://localhost:3004;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location /ws/ {
|
||||
proxy_pass http://localhost:8090;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
|
||||
proxy_read_timeout 86400;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
}
|
||||
|
||||
location /storage/ {
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods GET,
|
9813
logs/backend.out
Normal file
9813
logs/backend.out
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,11 +6,58 @@ export const config = {
|
||||
port: process.env.PORT || 8080,
|
||||
defaultStorage: process.env.DEFAULT_STORAGE || 'https://dev3.4nkweb.com/storage',
|
||||
appHost: process.env.APP_HOST || 'http://localhost:3000',
|
||||
|
||||
cors: {
|
||||
origin: 'http://localhost:3000',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'x-session-id', 'Authorization'],
|
||||
credentials: true
|
||||
}
|
||||
|
||||
// Déterminer dynamiquement l'origine CORS à partir d'APP_HOST si possible
|
||||
cors: (() => {
|
||||
// Build a whitelist of allowed origins
|
||||
const envList = (process.env.CORS_ORIGINS || process.env.CORS_ORIGIN || '')
|
||||
.split(',')
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
// Include APP_HOST origin if present
|
||||
try {
|
||||
const appHost = process.env.APP_HOST || 'http://localhost:3000';
|
||||
const appOrigin = new URL(appHost).origin;
|
||||
if (!envList.includes(appOrigin)) envList.push(appOrigin);
|
||||
} catch {}
|
||||
|
||||
// Common dev frontends (dev4 and local redirection constraint)
|
||||
const defaults = ['https://dev4.4nkweb.com', 'https://lecoffreio.4nkweb.com'];
|
||||
for (const o of defaults) {
|
||||
if (!envList.includes(o)) envList.push(o);
|
||||
}
|
||||
|
||||
// Regex allow-list from env (comma-separated), fallback to *.4nkweb.com and lecoffreio.4nkweb.com
|
||||
const regexFromEnv = (process.env.CORS_ORIGINS_REGEX || '')
|
||||
.split(',')
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean)
|
||||
.map(pattern => {
|
||||
try { return new RegExp(pattern); } catch { return null; }
|
||||
})
|
||||
.filter((r): r is RegExp => !!r);
|
||||
|
||||
const defaultRegexList: RegExp[] = [
|
||||
/^https:\/\/.*\.4nkweb\.com$/,
|
||||
/^http:\/\/local\.4nkweb\.com:3000$/
|
||||
];
|
||||
|
||||
const regexList = regexFromEnv.length > 0 ? regexFromEnv : defaultRegexList;
|
||||
|
||||
// Always allow same-origin or non-browser clients (no Origin header)
|
||||
const origin = (requestOrigin: string | undefined, callback: (err: Error | null, allow?: boolean | string | RegExp | (string | RegExp)[]) => void) => {
|
||||
if (!requestOrigin) return callback(null, true);
|
||||
if (envList.includes(requestOrigin)) return callback(null, requestOrigin);
|
||||
if (regexList.some(re => re.test(requestOrigin))) return callback(null, requestOrigin);
|
||||
return callback(null, false);
|
||||
};
|
||||
|
||||
return {
|
||||
origin,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'x-session-id', 'Authorization'],
|
||||
credentials: true
|
||||
};
|
||||
})()
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ import { NotFoundError, ExternalServiceError, BusinessRuleError } from '../types
|
||||
* without depending on Express Request/Response objects
|
||||
*/
|
||||
export class IdNotController {
|
||||
|
||||
|
||||
/**
|
||||
* Get user rattachements by idNot
|
||||
*/
|
||||
@ -51,9 +51,9 @@ export class IdNotController {
|
||||
}
|
||||
}));
|
||||
|
||||
Logger.info('Successfully retrieved user rattachements', {
|
||||
Logger.info('Successfully retrieved user rattachements', {
|
||||
idNot,
|
||||
count: officeData.length
|
||||
count: officeData.length
|
||||
});
|
||||
|
||||
return officeData;
|
||||
@ -67,12 +67,12 @@ export class IdNotController {
|
||||
|
||||
try {
|
||||
const result = await IdNotService.getOfficeRattachements(idNot);
|
||||
|
||||
Logger.info('Successfully retrieved office rattachements', {
|
||||
idNot,
|
||||
count: result.result?.length || 0
|
||||
|
||||
Logger.info('Successfully retrieved office rattachements', {
|
||||
idNot,
|
||||
count: result.result?.length || 0
|
||||
});
|
||||
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
Logger.error('Failed to get office rattachements', {
|
||||
@ -93,6 +93,12 @@ export class IdNotController {
|
||||
// Exchange code for tokens
|
||||
const tokens = await IdNotService.exchangeCodeForTokens(code);
|
||||
|
||||
Logger.info('Token exchange successful', {
|
||||
hasAccessToken: !!tokens.access_token,
|
||||
hasIdToken: !!tokens.id_token,
|
||||
tokenKeys: Object.keys(tokens)
|
||||
});
|
||||
|
||||
const jwt = tokens.id_token;
|
||||
if (!jwt) {
|
||||
throw new BusinessRuleError('No ID token received from IdNot');
|
||||
@ -101,16 +107,23 @@ export class IdNotController {
|
||||
// Decode JWT payload
|
||||
const payload = JSON.parse(Buffer.from(jwt.split('.')[1], 'base64').toString('utf8'));
|
||||
|
||||
Logger.info('JWT payload decoded', {
|
||||
payloadKeys: Object.keys(payload),
|
||||
hasProfileIdn: !!payload.profile_idn,
|
||||
hasSub: !!payload.sub,
|
||||
hasEntityIdn: !!payload.entity_idn
|
||||
});
|
||||
|
||||
// Get user data
|
||||
const userData = await IdNotService.getUserData(payload.profile_idn);
|
||||
|
||||
|
||||
if (!userData || !userData.statutDuRattachement || userData.entite.typeEntite.name !== 'office') {
|
||||
throw new BusinessRuleError('User not attached to an office');
|
||||
}
|
||||
|
||||
// Get office location data
|
||||
const officeLocationData = await IdNotService.getOfficeLocationData(userData.entite.locationsUrl);
|
||||
|
||||
|
||||
if (!officeLocationData || !officeLocationData.result || officeLocationData.result.length === 0) {
|
||||
throw new BusinessRuleError('Office location data not found');
|
||||
}
|
||||
@ -157,10 +170,10 @@ export class IdNotController {
|
||||
createdAt: Date.now(),
|
||||
expiresAt: Date.now() + (24 * 60 * 60 * 1000) // 24 hours
|
||||
};
|
||||
|
||||
|
||||
authTokens.push(tokenData);
|
||||
|
||||
Logger.info('IdNot authentication successful', {
|
||||
Logger.info('IdNot authentication successful', {
|
||||
idNot: idNotUser.idNot,
|
||||
office: idNotUser.office.name
|
||||
});
|
||||
@ -172,11 +185,11 @@ export class IdNotController {
|
||||
codePrefix: code.substring(0, 8) + '...',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
|
||||
|
||||
if (error instanceof BusinessRuleError || error instanceof ExternalServiceError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
throw new ExternalServiceError('IdNot', `Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
@ -189,12 +202,12 @@ export class IdNotController {
|
||||
|
||||
// Find the full token data
|
||||
const userAuth = authTokens.find(auth => auth.authToken === authToken);
|
||||
|
||||
|
||||
if (!userAuth || !userAuth.idNotUser) {
|
||||
throw new NotFoundError('User data not found. Please log in again.');
|
||||
}
|
||||
|
||||
Logger.info('Current user data retrieved', {
|
||||
Logger.info('Current user data retrieved', {
|
||||
idNot,
|
||||
office: userAuth.idNotUser.office.name
|
||||
});
|
||||
@ -213,16 +226,16 @@ export class IdNotController {
|
||||
|
||||
// Remove the auth token from the array
|
||||
const tokenIndex = authTokens.findIndex(auth => auth.authToken === authToken);
|
||||
|
||||
|
||||
if (tokenIndex > -1) {
|
||||
const removedToken = authTokens.splice(tokenIndex, 1)[0];
|
||||
Logger.info('User logout successful', {
|
||||
idNot: removedToken.idNot
|
||||
Logger.info('User logout successful', {
|
||||
idNot: removedToken.idNot
|
||||
});
|
||||
} else {
|
||||
Logger.warn('Logout attempted with invalid token');
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Déconnexion réussie'
|
||||
|
55
src/handlers/idnot-callback.handlers.ts
Normal file
55
src/handlers/idnot-callback.handlers.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { asyncHandler } from '../middleware/error-handler';
|
||||
import { StateService } from '../services/state.service';
|
||||
import { IdNotController } from '../controllers/idnot.controller';
|
||||
import { ExternalServiceError, ValidationError } from '../types/errors';
|
||||
|
||||
export class IdNotCallbackHandlers {
|
||||
static callback = asyncHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const code = req.query.code as string | undefined;
|
||||
const state = req.query.state as string | undefined;
|
||||
|
||||
// Debug logging to inspect what reaches the backend via Nginx
|
||||
// Note: keep logs concise to avoid leaking sensitive data in production
|
||||
try {
|
||||
// Only log presence/length of params to avoid full values in logs
|
||||
const safeQuery = {
|
||||
code_present: typeof code === 'string',
|
||||
code_length: typeof code === 'string' ? code.length : undefined,
|
||||
state_present: typeof state === 'string',
|
||||
state_length: typeof state === 'string' ? state.length : undefined
|
||||
};
|
||||
console.info('[IdNotCallback] incoming request', {
|
||||
originalUrl: req.originalUrl,
|
||||
method: req.method,
|
||||
query: safeQuery,
|
||||
headers: {
|
||||
host: req.headers.host,
|
||||
'x-forwarded-for': req.headers['x-forwarded-for'],
|
||||
'x-forwarded-proto': req.headers['x-forwarded-proto']
|
||||
}
|
||||
});
|
||||
} catch {}
|
||||
|
||||
if (!code || !state) {
|
||||
throw new ValidationError('Missing code or state', [
|
||||
{ field: 'code', value: code, constraints: ['required'] },
|
||||
{ field: 'state', value: state, constraints: ['required'] }
|
||||
]);
|
||||
}
|
||||
|
||||
const payload = StateService.verifyState(state);
|
||||
|
||||
// Exchange code using existing controller logic to build auth and user
|
||||
const { authToken } = await IdNotController.authenticate(code);
|
||||
|
||||
const url = new URL(payload.next_url);
|
||||
// Prefer fragment to avoid leaking in server logs
|
||||
const hash = url.hash ? url.hash.replace(/^#/, '') + `&authToken=${encodeURIComponent(authToken)}` : `authToken=${encodeURIComponent(authToken)}`;
|
||||
const redirectTo = `${url.origin}${url.pathname}${url.search}#${hash}`;
|
||||
|
||||
res.redirect(302, redirectTo);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,21 +4,21 @@ import { asyncHandler } from '../middleware/error-handler';
|
||||
import { ValidationError, BusinessRuleError } from '../types/errors';
|
||||
|
||||
/**
|
||||
* Route handlers that extract and validate HTTP request data
|
||||
* Route handlers that extract and validate HTTP request data
|
||||
* before calling pure controller methods
|
||||
*/
|
||||
export class IdNotHandlers {
|
||||
|
||||
|
||||
/**
|
||||
* GET /user/rattachements
|
||||
* Extract idNot from query params and call controller
|
||||
*/
|
||||
static getUserRattachements = asyncHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const requestId = req.headers['x-request-id'] as string;
|
||||
|
||||
|
||||
// Extract and validate parameters
|
||||
const { idNot } = req.query;
|
||||
|
||||
|
||||
if (!idNot || typeof idNot !== 'string') {
|
||||
throw new ValidationError('idNot parameter is required', [
|
||||
{ field: 'idNot', value: idNot, constraints: ['Must be a valid string'] }
|
||||
@ -27,20 +27,20 @@ export class IdNotHandlers {
|
||||
|
||||
// Call pure controller method with extracted parameters
|
||||
const result = await IdNotController.getUserRattachements(idNot);
|
||||
|
||||
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /office/rattachements
|
||||
* GET /office/rattachements
|
||||
* Extract idNot from query params and call controller
|
||||
*/
|
||||
static getOfficeRattachements = asyncHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const requestId = req.headers['x-request-id'] as string;
|
||||
|
||||
|
||||
// Extract and validate parameters
|
||||
const { idNot } = req.query;
|
||||
|
||||
|
||||
if (!idNot || typeof idNot !== 'string') {
|
||||
throw new ValidationError('idNot parameter is required', [
|
||||
{ field: 'idNot', value: idNot, constraints: ['Must be a valid string'] }
|
||||
@ -49,20 +49,20 @@ export class IdNotHandlers {
|
||||
|
||||
// Call pure controller method
|
||||
const result = await IdNotController.getOfficeRattachements(idNot);
|
||||
|
||||
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /auth/:code
|
||||
* Extract code from URL params and call controller
|
||||
* POST /auth
|
||||
* Extract code from request body and call controller
|
||||
*/
|
||||
static authenticate = asyncHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const requestId = req.headers['x-request-id'] as string;
|
||||
|
||||
// Extract and validate parameters
|
||||
const { code } = req.params;
|
||||
|
||||
|
||||
// Extract and validate parameters from body
|
||||
const { code } = req.body;
|
||||
|
||||
if (!code || typeof code !== 'string' || code.length < 10) {
|
||||
throw new ValidationError('Invalid authentication code', [
|
||||
{ field: 'code', value: code, constraints: ['Must be a valid authorization code'] }
|
||||
@ -71,7 +71,7 @@ export class IdNotHandlers {
|
||||
|
||||
// Call pure controller method
|
||||
const result = await IdNotController.authenticate(code);
|
||||
|
||||
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
@ -81,17 +81,17 @@ export class IdNotHandlers {
|
||||
*/
|
||||
static getCurrentUser = asyncHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const requestId = req.headers['x-request-id'] as string;
|
||||
|
||||
|
||||
// Extract user info (set by authenticateIdNot middleware)
|
||||
if (!req.idNotUser) {
|
||||
throw new BusinessRuleError('User authentication required', undefined, requestId);
|
||||
}
|
||||
|
||||
const { idNot, authToken } = req.idNotUser;
|
||||
|
||||
|
||||
// Call pure controller method
|
||||
const result = await IdNotController.getCurrentUser(idNot, authToken);
|
||||
|
||||
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
@ -101,16 +101,16 @@ export class IdNotHandlers {
|
||||
*/
|
||||
static logout = asyncHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const requestId = req.headers['x-request-id'] as string;
|
||||
|
||||
|
||||
if (!req.idNotUser) {
|
||||
throw new BusinessRuleError('User authentication required', undefined, requestId);
|
||||
}
|
||||
|
||||
const { authToken } = req.idNotUser;
|
||||
|
||||
|
||||
// Call pure controller method
|
||||
const result = await IdNotController.logout(authToken);
|
||||
|
||||
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
@ -120,16 +120,16 @@ export class IdNotHandlers {
|
||||
*/
|
||||
static validateToken = asyncHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const requestId = req.headers['x-request-id'] as string;
|
||||
|
||||
|
||||
if (!req.idNotUser) {
|
||||
throw new BusinessRuleError('User authentication required', undefined, requestId);
|
||||
}
|
||||
|
||||
const { idNot } = req.idNotUser;
|
||||
|
||||
|
||||
// Call pure controller method
|
||||
const result = await IdNotController.validateToken(idNot);
|
||||
|
||||
|
||||
res.json(result);
|
||||
});
|
||||
}
|
||||
|
24
src/handlers/state.handlers.ts
Normal file
24
src/handlers/state.handlers.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { asyncHandler } from '../middleware/error-handler';
|
||||
import { StateService } from '../services/state.service';
|
||||
import { ValidationError } from '../types/errors';
|
||||
|
||||
export class StateHandlers {
|
||||
static createState = asyncHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const requestId = req.headers['x-request-id'] as string;
|
||||
const { next_url } = req.body || {};
|
||||
|
||||
if (!next_url || typeof next_url !== 'string') {
|
||||
throw new ValidationError('next_url is required', [
|
||||
{ field: 'next_url', value: next_url, constraints: ['Must be a valid string URL'] }
|
||||
], requestId);
|
||||
}
|
||||
|
||||
const state = StateService.signState(next_url);
|
||||
res.json({ state });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@ const router = Router();
|
||||
|
||||
router.get('/user/rattachements', IdNotHandlers.getUserRattachements);
|
||||
router.get('/office/rattachements', IdNotHandlers.getOfficeRattachements);
|
||||
router.post('/auth/:code', IdNotHandlers.authenticate);
|
||||
router.post('/auth', IdNotHandlers.authenticate);
|
||||
|
||||
router.get('/user', authenticateIdNot, IdNotHandlers.getCurrentUser);
|
||||
router.post('/logout', authenticateIdNot, IdNotHandlers.logout);
|
||||
|
@ -2,6 +2,8 @@ import { Router } from 'express';
|
||||
import { healthRoutes } from './health.routes';
|
||||
import { smsRoutes } from './sms.routes';
|
||||
import { idnotRoutes } from './idnot.routes';
|
||||
import { StateHandlers } from '../handlers/state.handlers';
|
||||
import { IdNotCallbackHandlers } from '../handlers/idnot-callback.handlers';
|
||||
import { emailRoutes } from './email.routes';
|
||||
import { stripeRoutes } from './stripe.routes';
|
||||
import { processRoutes } from './process.routes';
|
||||
@ -16,4 +18,8 @@ router.use('/api/v1/process', processRoutes);
|
||||
router.use('/api', emailRoutes);
|
||||
router.use('/api', stripeRoutes);
|
||||
|
||||
// State and callback endpoints (front-agnostic)
|
||||
router.post('/api/v1/idnot/state', StateHandlers.createState);
|
||||
router.get('/idnot/callback', IdNotCallbackHandlers.callback);
|
||||
|
||||
export { router as routes };
|
||||
|
23
src/routes/index.ts.bak
Normal file
23
src/routes/index.ts.bak
Normal file
@ -0,0 +1,23 @@
|
||||
import { Router } from 'express';
|
||||
import { healthRoutes } from './health.routes';
|
||||
import { smsRoutes } from './sms.routes';
|
||||
import { idnotRoutes } from './idnot.routes';
|
||||
import { emailRoutes } from './email.routes';
|
||||
import { stripeRoutes } from './stripe.routes';
|
||||
import { processRoutes } from './process.routes';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Mount routes
|
||||
router.use('/api/v1', healthRoutes);
|
||||
router.use('/api', smsRoutes);
|
||||
router.use('/api/v1/idnot', idnotRoutes);
|
||||
router.use('/api/v1/process', processRoutes);
|
||||
router.use('/api', emailRoutes);
|
||||
router.use('/api', stripeRoutes);
|
||||
|
||||
export { router as routes };
|
||||
|
||||
|
||||
|
||||
|
@ -22,6 +22,15 @@ export class IdNotService {
|
||||
code
|
||||
};
|
||||
|
||||
console.log('🔍 IdNot Token Request Debug:', {
|
||||
url: IDNOT_TOKEN_URL,
|
||||
client_id: IDNOT_CLIENT_ID,
|
||||
redirect_uri: IDNOT_REDIRECT_URI,
|
||||
grant_type: 'authorization_code',
|
||||
code_length: code.length,
|
||||
code_prefix: code.substring(0, 8) + '...'
|
||||
});
|
||||
|
||||
const response = await fetch(IDNOT_TOKEN_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -30,6 +39,12 @@ export class IdNotService {
|
||||
body: new URLSearchParams(params).toString()
|
||||
});
|
||||
|
||||
console.log('🔍 IdNot Token Response Debug:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: Object.fromEntries(response.headers.entries())
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Token exchange failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
119
src/services/state.service.ts
Normal file
119
src/services/state.service.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
type StatePayload = {
|
||||
next_url: string;
|
||||
nonce: string;
|
||||
ts: number;
|
||||
};
|
||||
|
||||
const nonceCache = new Map<string, number>();
|
||||
|
||||
function base64url(input: Buffer | string): string {
|
||||
const buf = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
||||
return buf.toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
||||
}
|
||||
|
||||
function hmacSha256(data: string, secret: string): string {
|
||||
return base64url(crypto.createHmac('sha256', secret).update(data).digest());
|
||||
}
|
||||
|
||||
function isLocalhostHost(host: string): boolean {
|
||||
return host === 'localhost' || host === '127.0.0.1';
|
||||
}
|
||||
|
||||
function validateNextUrl(nextUrl: string): void {
|
||||
const allowLocalhost = (process.env.ALLOW_LOCALHOST_REDIRECTS || 'false').toLowerCase() === 'true';
|
||||
const patterns = (process.env.ALLOWED_REDIRECT_HOST_PATTERNS || '')
|
||||
.split(',')
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean)
|
||||
.map(p => new RegExp(p));
|
||||
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(nextUrl);
|
||||
} catch {
|
||||
throw new Error('Invalid next_url');
|
||||
}
|
||||
|
||||
if (url.protocol !== 'https:' && !(allowLocalhost && url.protocol === 'http:' && isLocalhostHost(url.hostname))) {
|
||||
throw new Error('next_url scheme not allowed');
|
||||
}
|
||||
|
||||
if (patterns.length > 0) {
|
||||
const ok = isLocalhostHost(url.hostname) || patterns.some(re => re.test(url.hostname));
|
||||
if (!ok) {
|
||||
throw new Error('next_url host not allowed');
|
||||
}
|
||||
}
|
||||
|
||||
if (nextUrl.length > 2048) {
|
||||
throw new Error('next_url too long');
|
||||
}
|
||||
}
|
||||
|
||||
export const StateService = {
|
||||
signState(nextUrl: string): string {
|
||||
validateNextUrl(nextUrl);
|
||||
|
||||
const secret = process.env.BACK_HMAC_SECRET;
|
||||
if (!secret) throw new Error('Missing BACK_HMAC_SECRET');
|
||||
|
||||
const payload: StatePayload = {
|
||||
next_url: nextUrl,
|
||||
nonce: crypto.randomBytes(16).toString('hex'),
|
||||
ts: Date.now()
|
||||
};
|
||||
|
||||
const payloadStr = JSON.stringify(payload);
|
||||
const signature = hmacSha256(payloadStr, secret);
|
||||
|
||||
// store nonce for replay protection
|
||||
const ttl = parseInt(process.env.STATE_TTL_SECONDS || '180', 10) * 1000;
|
||||
nonceCache.set(payload.nonce, payload.ts + ttl);
|
||||
|
||||
return `${base64url(payloadStr)}.${signature}`;
|
||||
},
|
||||
|
||||
verifyState(state: string): StatePayload {
|
||||
const secret = process.env.BACK_HMAC_SECRET;
|
||||
if (!secret) throw new Error('Missing BACK_HMAC_SECRET');
|
||||
|
||||
const parts = state.split('.');
|
||||
if (parts.length !== 2) throw new Error('Invalid state format');
|
||||
const [b64, sig] = parts;
|
||||
const payloadJson = Buffer.from(b64.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
|
||||
const expectedSig = hmacSha256(payloadJson, secret);
|
||||
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expectedSig))) {
|
||||
throw new Error('Invalid state signature');
|
||||
}
|
||||
|
||||
const payload = JSON.parse(payloadJson) as StatePayload;
|
||||
|
||||
// TTL
|
||||
const ttlMs = parseInt(process.env.STATE_TTL_SECONDS || '180', 10) * 1000;
|
||||
if (Date.now() - payload.ts > ttlMs) throw new Error('State expired');
|
||||
|
||||
// replay protection
|
||||
const expiry = nonceCache.get(payload.nonce);
|
||||
if (!expiry) throw new Error('Invalid nonce');
|
||||
nonceCache.delete(payload.nonce);
|
||||
|
||||
// validate next_url again
|
||||
validateNextUrl(payload.next_url);
|
||||
|
||||
return payload;
|
||||
}
|
||||
};
|
||||
|
||||
// periodic cleanup
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const [nonce, expiry] of nonceCache.entries()) {
|
||||
if (expiry <= now) nonceCache.delete(nonce);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user