1
0
forked from baron/baron-sso

3 Commits

Author SHA1 Message Date
ai-cell-a100-1
7ad1743758 모바일 승인 완료 화면에서 verify_failed 오류 노출 개선 2026-05-19 17:40:27 +09:00
ai-cell-a100-1
5376baf6d8 rebase 전 변경된 파일 추가 2026-04-24 15:22:46 +09:00
ai-cell-a100-1
7ecb19e397 fc 2026-04-24 15:22:45 +09:00
25 changed files with 3712 additions and 1714 deletions

1886
.env.test2 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,8 @@ import { UserManager, WebStorageStateStore } from "oidc-client-ts";
import type { AuthProviderProps } from "react-oidc-context";
export const oidcConfig: AuthProviderProps = {
authority: import.meta.env.VITE_OIDC_AUTHORITY || "https://sso.hmac.kr/oidc", // Gateway Proxy URL
authority:
import.meta.env.VITE_OIDC_AUTHORITY || `${window.location.protocol}//${window.location.hostname}:{{USERFRONT_PORT}}/oidc`,
client_id: import.meta.env.VITE_OIDC_CLIENT_ID || "adminfront",
redirect_uri: `${window.location.origin}/auth/callback`,
response_type: "code",

View File

@@ -6,10 +6,11 @@ export default defineConfig({
envPrefix: ["VITE_", "USERFRONT_"],
server: {
host: "127.0.0.1",
allowedHosts: ["sadmin.hmac.kr", "localhost", "172.16.10.176", "127.0.0.1"],
// 인스턴스별 도메인을 자동으로 허용
allowedHosts: ["{{ADMINFRONT_DOMAIN}}", "localhost", "127.0.0.1","adminb.hmac.kr"],
proxy: {
"/api": {
target: process.env.API_PROXY_TARGET || "http://localhost:3000",
target: process.env.API_PROXY_TARGET || "http://backend:{{BACKEND_PORT}}",
changeOrigin: true,
},
},
@@ -17,10 +18,10 @@ export default defineConfig({
preview: {
host: "127.0.0.1",
port: 5173,
allowedHosts: ["sadmin.hmac.kr", "localhost", "172.16.10.176", "127.0.0.1"],
allowedHosts: ["{{ADMINFRONT_DOMAIN}}", "localhost", "127.0.0.1", "adminb.hmac.kr"],
proxy: {
"/api": {
target: process.env.API_PROXY_TARGET || "http://localhost:3000",
target: process.env.API_PROXY_TARGET || "http://backend:{{BACKEND_PORT}}",
changeOrigin: true,
},
},

75
compose.infra.test2.yaml Normal file
View File

@@ -0,0 +1,75 @@
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: ${DB_USER:-baron}
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
POSTGRES_DB: ${DB_NAME:-baron_sso}
ports:
- "${DB_PORT:-15432}:5432"
volumes:
- postgres_data_test2:/var/lib/postgresql/data
- ./docker/init-metadata:/docker-entrypoint-initdb.d
networks:
- test2_net
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${DB_USER:-baron} -d ${DB_NAME:-baron_sso}",
]
interval: 5s
timeout: 5s
retries: 5
restart: always
clickhouse:
image: clickhouse/clickhouse-server:latest
restart: always
ports:
- "${CLICKHOUSE_PORT_HTTP:-28123}:8123"
- "${CLICKHOUSE_PORT_NATIVE:-29000}:9000"
volumes:
- clickhouse_data_test2:/var/lib/clickhouse
environment:
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-baron}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-password}
networks:
- test2_net
redis:
image: redis:7-alpine
restart: always
command: redis-server --port 6399
ports:
- "${REDIS_PORT:-16399}:6399"
volumes:
- redis_data_test2:/data
networks:
- test2_net
gateway:
build:
context: ./gateway
dockerfile: Dockerfile
restart: always
ports:
- "25000:5000"
networks:
- test2_net
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
volumes:
postgres_data_test2:
clickhouse_data_test2:
redis_data_test2:
networks:
test2_net:
name: test2_net
driver: bridge

267
compose.ory.test2.yaml Normal file
View File

@@ -0,0 +1,267 @@
services:
postgres_ory:
image: postgres:${ORY_POSTGRES_TAG:-17-alpine}
environment:
- POSTGRES_USER=${ORY_POSTGRES_USER:-ory}
- POSTGRES_PASSWORD=${ORY_POSTGRES_PASSWORD:-secret}
- POSTGRES_DB=${ORY_POSTGRES_DB:-ory}
volumes:
- ./docker/ory/init-db:/docker-entrypoint-initdb.d
- ory_postgres_data_test2:/var/lib/postgresql/data
networks:
- test2_net
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${ORY_POSTGRES_USER:-ory} -d ${KRATOS_DB:-ory_kratos}",
]
interval: 5s
timeout: 5s
retries: 5
# --- Kratos ---
kratos-migrate:
image: oryd/kratos:${KRATOS_VERSION:-v25.4.0}
env_file:
- .env.test2
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD:-secret}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- KRATOS_SESSION_COOKIE_DOMAIN=${KRATOS_SESSION_COOKIE_DOMAIN:-hmac.kr}
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL:-http://localhost:15000/auth}
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:15000}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["http://localhost:15000","http://localhost:15000/"]}
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/error
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/error?error=settings_disabled
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/recovery
- KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/verification
- KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/login
- KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/registration
- KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:15000}/login
volumes:
- ./docker/ory/kratos:/etc/config/kratos
command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
depends_on:
postgres_ory:
condition: service_healthy
networks:
- test2_net
kratos:
image: oryd/kratos:${KRATOS_VERSION:-v25.4.0}
env_file:
- .env.test2
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD:-secret}@postgres_ory:5432/${KRATOS_DB:-ory_kratos}?sslmode=disable&max_conns=20
- COOKIE_SECRET=${COOKIE_SECRET:-localcookie123}
- KRATOS_SESSION_COOKIE_DOMAIN=${KRATOS_SESSION_COOKIE_DOMAIN:-hmac.kr}
- KRATOS_SERVE_PUBLIC_BASE_URL=${KRATOS_BROWSER_URL:-http://localhost:15000/auth}
- KRATOS_SERVE_ADMIN_BASE_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
- KRATOS_SELFSERVICE_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:15000}
- KRATOS_SELFSERVICE_ALLOWED_RETURN_URLS=${KRATOS_ALLOWED_RETURN_URLS_JSON:-["http://localhost:15000","http://localhost:15000/"]}
- KRATOS_SELFSERVICE_FLOWS_ERROR_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/error
- KRATOS_SELFSERVICE_FLOWS_SETTINGS_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/error?error=settings_disabled
- KRATOS_SELFSERVICE_FLOWS_RECOVERY_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/recovery
- KRATOS_SELFSERVICE_FLOWS_VERIFICATION_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/verification
- KRATOS_SELFSERVICE_FLOWS_LOGIN_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/login
- KRATOS_SELFSERVICE_FLOWS_REGISTRATION_UI_URL=${KRATOS_UI_URL:-http://localhost:15000}/registration
- KRATOS_SELFSERVICE_FLOWS_LOGOUT_AFTER_DEFAULT_BROWSER_RETURN_URL=${KRATOS_UI_URL:-http://localhost:15000}/login
volumes:
- ./docker/ory/kratos:/etc/config/kratos
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
depends_on:
kratos-migrate:
condition: service_completed_successfully
networks:
- test2_net
# --- Hydra ---
hydra-migrate:
image: oryd/hydra:${HYDRA_VERSION:-v25.4.0}
env_file:
- .env.test2
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD:-secret}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
command: migrate sql up -e --yes
depends_on:
postgres_ory:
condition: service_healthy
networks:
- test2_net
hydra:
image: oryd/hydra:${HYDRA_VERSION:-v25.4.0}
env_file:
- .env.test2
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD:-secret}@postgres_ory:5432/${HYDRA_DB:-ory_hydra}?sslmode=disable&max_conns=20
- URLS_SELF_ISSUER=${USERFRONT_URL:-http://localhost:5000}/oidc
- URLS_LOGIN=${USERFRONT_URL:-http://localhost:5000}/login
- URLS_CONSENT=${USERFRONT_URL:-http://localhost:5000}/consent
- URLS_ERROR=${USERFRONT_URL:-http://localhost:5000}/error
- SECRETS_SYSTEM=${ORY_POSTGRES_PASSWORD}
volumes:
- ./docker/ory/hydra:/etc/config/hydra
command: serve -c /etc/config/hydra/hydra.yml all --dev
depends_on:
hydra-migrate:
condition: service_completed_successfully
networks:
- test2_net
# --- Keto ---
keto-migrate:
image: oryd/keto:${KETO_VERSION:-v25.4.0}
env_file:
- .env.test2
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD:-secret}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
volumes:
- ./docker/ory/keto:/etc/config/keto
command: ["migrate", "up", "-c", "/etc/config/keto/keto.yml", "--yes"]
depends_on:
postgres_ory:
condition: service_healthy
networks:
- test2_net
keto:
image: oryd/keto:${KETO_VERSION:-v25.4.0}
env_file:
- .env.test2
environment:
- DSN=postgres://${ORY_POSTGRES_USER:-ory}:${ORY_POSTGRES_PASSWORD:-secret}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20
volumes:
- ./docker/ory/keto:/etc/config/keto
command: serve -c /etc/config/keto/keto.yml
depends_on:
keto-migrate:
condition: service_completed_successfully
networks:
- test2_net
# --- Oathkeeper ---
oathkeeper:
image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v25.4.0}
user: "${OATHKEEPER_UID:-1001}:${OATHKEEPER_GID:-1001}"
env_file:
- .env.test2
ports:
- "${OATHKEEPER_PROXY_PORT:-14467}:4455" # Proxy
environment:
- APP_ENV=${APP_ENV:-development}
- LOG_LEVEL=debug
- OATHKEEPER_INTROSPECT_CLIENT_ID=${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect}
- OATHKEEPER_INTROSPECT_CLIENT_SECRET=${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret}
volumes:
- ./docker/ory/oathkeeper:/etc/config/oathkeeper
- oathkeeper_logs_test2:/var/log/oathkeeper
entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"]
networks:
- test2_net
ory_clickhouse:
image: clickhouse/clickhouse-server:latest
environment:
- CLICKHOUSE_USER=${ORY_CLICKHOUSE_USER:-ory}
- CLICKHOUSE_PASSWORD=${ORY_CLICKHOUSE_PASSWORD:-orypass}
volumes:
- ory_clickhouse_data_test2:/var/lib/clickhouse
- ./docker/ory/clickhouse:/docker-entrypoint-initdb.d
networks:
- test2_net
ory_vector:
image: timberio/vector:0.36.0-alpine
volumes:
- ./docker/ory/vector:/etc/vector
- oathkeeper_logs_test2:/var/log/oathkeeper
command: ["-c", "/etc/vector/vector.toml"]
depends_on:
- oathkeeper
- ory_clickhouse
networks:
- test2_net
# --- 초기화 & 헬스체크 ---
ory_stack_check:
image: alpine:latest
command: >
/bin/sh -c "
apk add --no-cache curl;
echo 'Wait for services...';
until curl -s http://kratos:4433/health/ready; do sleep 1; done;
until curl -s http://hydra:4444/health/ready; do sleep 1; done;
until curl -s http://keto:4466/health/ready; do sleep 1; done;
echo 'Ory Stack is fully operational!';"
depends_on:
- kratos
- hydra
- keto
networks:
- test2_net
# 기본 RP (Admin Front 등) 자동 등록 컨테이너
init-rp:
image: oryd/hydra:v25.4.0
entrypoint: ["/bin/sh"]
command:
- -ec
- |
hydra delete oauth2-client --endpoint http://hydra:4445 adminfront >/dev/null 2>&1 || true
hydra delete oauth2-client --endpoint http://hydra:4445 devfront >/dev/null 2>&1 || true
hydra delete oauth2-client --endpoint http://hydra:4445 ${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect} >/dev/null 2>&1 || true
# --- AdminFront ---
hydra delete oauth2-client --endpoint http://hydra:4445 adminfront || true
hydra create oauth2-client \
--endpoint http://hydra:4445 \
--id adminfront \
--name "AdminFront" \
--grant-type authorization_code,refresh_token \
--response-type code \
--scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \
--redirect-uri https://ssob.hmac.kr/auth/callback \
--redirect-uri https://adminb.hmac.kr/auth/callback \
--redirect-uri http://localhost:25173/auth/callback \
--redirect-uri http://adminb.hmac.kr:25173/auth/callback
# --- DevFront ---
hydra delete oauth2-client --endpoint http://hydra:4445 devfront || true
hydra create oauth2-client \
--endpoint http://hydra:4445 \
--id devfront \
--name "DevFront" \
--grant-type authorization_code,refresh_token \
--response-type code \
--scope openid,offline_access,profile,email \
--token-endpoint-auth-method none \
--redirect-uri https://ssob.hmac.kr/auth/callback \
--redirect-uri https://devb.hmac.kr/auth/callback \
--redirect-uri http://localhost:25174/auth/callback \
--redirect-uri http://devb.hmac.kr:25174/auth/callback
hydra create oauth2-client \
--endpoint http://hydra:4445 \
--id ${OATHKEEPER_INTROSPECT_CLIENT_ID:-oathkeeper-introspect} \
--secret ${OATHKEEPER_INTROSPECT_CLIENT_SECRET:-oathkeeper-secret} \
--grant-type client_credentials \
--response-type token \
--scope openid,offline_access,profile,email
depends_on:
ory_stack_check:
condition: service_completed_successfully
networks:
- test2_net
volumes:
ory_postgres_data_test2:
ory_clickhouse_data_test2:
oathkeeper_logs_test2:
networks:
test2_net:
name: test2_net
driver: bridge

0
deploy/create-instance.sh Normal file → Executable file
View File

View File

@@ -2,7 +2,8 @@ import { UserManager, WebStorageStateStore } from "oidc-client-ts";
import type { AuthProviderProps } from "react-oidc-context";
export const oidcConfig: AuthProviderProps = {
authority: import.meta.env.VITE_OIDC_AUTHORITY || "https://sso.hmac.kr/oidc", // Gateway Proxy URL
authority:
import.meta.env.VITE_OIDC_AUTHORITY || `${window.location.protocol}//${window.location.hostname}:{{USERFRONT_PORT}}/oidc`,
client_id: import.meta.env.VITE_OIDC_CLIENT_ID || "devfront",
redirect_uri: `${window.location.origin}/auth/callback`,
response_type: "code",

View File

@@ -3,12 +3,14 @@ import { defineConfig } from "vite";
export default defineConfig({
plugins: [react()],
envPrefix: ["VITE_", "USERFRONT_"],
server: {
host: "127.0.0.1",
allowedHosts: ["sdev.hmac.kr", "localhost", "172.16.10.176", "127.0.0.1"],
// 인스턴스별 도메인을 자동으로 허용
allowedHosts: ["{{DEVFRONT_DOMAIN}}", "localhost", "127.0.0.1","devb.hmac.kr"],
proxy: {
"/api": {
target: process.env.API_PROXY_TARGET || "http://localhost:3000",
target: process.env.API_PROXY_TARGET || "http://backend:{{BACKEND_PORT}}",
changeOrigin: true,
},
},
@@ -16,10 +18,10 @@ export default defineConfig({
preview: {
host: "127.0.0.1",
port: 5173,
allowedHosts: ["sdev.hmac.kr", "localhost", "172.16.10.176", "127.0.0.1"],
allowedHosts: ["{{DEVFRONT_DOMAIN}}", "localhost", "127.0.0.1","devb.hmac.kr"],
proxy: {
"/api": {
target: process.env.API_PROXY_TARGET || "http://localhost:3000",
target: process.env.API_PROXY_TARGET || "http://backend:{{BACKEND_PORT}}",
changeOrigin: true,
},
},

130
docker-compose.test2.yaml Normal file
View File

@@ -0,0 +1,130 @@
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
env_file:
- .env.test2
- .generated/auth-config.test2.env
environment:
- APP_ENV=${APP_ENV:-development}
- GO_ENV=${APP_ENV:-development}
- COOKIE_SECRET=${COOKIE_SECRET}
- JWT_SECRET=${JWT_SECRET}
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=baron_sso
- CLICKHOUSE_HOST=clickhouse
- CLICKHOUSE_PORT=9000
- CLICKHOUSE_PORT_NATIVE=9000
- REDIS_ADDR=redis:6399
- KRATOS_ADMIN_URL=${KRATOS_ADMIN_URL:-http://kratos:4434}
- HYDRA_ADMIN_URL=${HYDRA_ADMIN_URL:-http://hydra:4445}
- HYDRA_PUBLIC_URL=${HYDRA_PUBLIC_URL:-http://hydra:4444}
- KETO_WRITE_URL=${KETO_WRITE_URL:-http://keto:4467}
- PORT=${BACKEND_PORT:-23000}
ports:
- "${BACKEND_PORT:-23000}:${BACKEND_PORT:-23000}"
depends_on:
infra_check:
condition: service_completed_successfully
networks:
- test2_net
volumes:
- ./backend:/app
command: ["go", "run", "./cmd/server"]
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:${BACKEND_PORT:-23000}/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
adminfront:
build:
context: ./adminfront
dockerfile: Dockerfile
env_file:
- .env.test2
- .generated/auth-config.test2.env
environment:
- APP_ENV=${APP_ENV:-development}
- API_PROXY_TARGET=http://backend:23000
- USERFRONT_URL=${USERFRONT_URL}
- VITE_OIDC_CLIENT_ID=adminfront
- VITE_OIDC_AUTHORITY=https://ssob.hmac.kr/oidc
ports:
- "${ADMINFRONT_PORT:-25173}:5173"
volumes:
- ./adminfront:/app
- ./locales:/locales
- /app/node_modules
networks:
- test2_net
devfront:
build:
context: ./devfront
dockerfile: Dockerfile
env_file:
- .env.test2
- .generated/auth-config.test2.env
environment:
- APP_ENV=${APP_ENV:-development}
- API_PROXY_TARGET=http://backend:23000
- USERFRONT_URL=${USERFRONT_URL}
- VITE_OIDC_CLIENT_ID=devfront
- VITE_OIDC_AUTHORITY=https://ssob.hmac.kr/oidc
ports:
- "${DEVFRONT_PORT:-25174}:5173"
volumes:
- ./devfront:/app
- ./locales:/locales
- /app/node_modules
networks:
- test2_net
userfront:
build:
context: .
dockerfile: userfront/Dockerfile
env_file:
- .env.test2
- .generated/auth-config.test2.env
environment:
- BACKEND_URL=${BACKEND_URL:-}
- USERFRONT_URL=${USERFRONT_URL}
- APP_ENV=${APP_ENV}
networks:
- test2_net
depends_on:
backend:
condition: service_healthy
command: >
/bin/sh -c "mkdir -p /usr/share/nginx/html/assets &&
echo \"BACKEND_URL=$${BACKEND_URL}\" >> /usr/share/nginx/html/assets/.env &&
echo \"USERFRONT_URL=$${USERFRONT_URL}\" >> /usr/share/nginx/html/assets/.env &&
echo \"APP_ENV=$${APP_ENV}\" >> /usr/share/nginx/html/assets/.env &&
cp /usr/share/nginx/html/assets/.env /usr/share/nginx/html/.env &&
nginx -g 'daemon off;'"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"]
interval: 10s
timeout: 5s
retries: 3
start_period: 10s
infra_check:
image: alpine
command: ["/bin/sh", "-c", "echo 'Waiting for postgres...'; until nc -z postgres 5432; do sleep 1; done; echo 'Postgres is up!'"]
depends_on:
postgres:
condition: service_healthy
networks:
- test2_net
networks:
test2_net:
name: test2_net
driver: bridge

View File

@@ -83,10 +83,10 @@ serve:
urls:
self:
issuer: http://127.0.0.1:4444
consent: http://127.0.0.1:3000/consent
login: http://127.0.0.1:3000/login
logout: http://127.0.0.1:3000/logout
issuer: ${URLS_SELF_ISSUER}
consent: ${URLS_CONSENT}
login: ${URLS_LOGIN}
logout: ${URLS_LOGOUT}
device:
verification: http://127.0.0.1:3000/device/verify
success: http://127.0.0.1:3000/device/success

View File

@@ -8,17 +8,7 @@ serve:
cors:
enabled: true
allowed_origins:
- http://localhost:5173
- http://localhost:5174
- http://localhost:5175
- http://localhost:5000
- http://backend:3000
- http://baron_backend:3000
- https://ssologin.hmac.kr
- https://sso-test.hmac.kr
- https://app.brsw.kr
- https://sss.hmac.kr
- https://sso.hmac.kr
- http://backend:23000
admin:
base_url: http://localhost:4434/
@@ -29,11 +19,11 @@ session:
path: /
selfservice:
default_browser_return_url: http://localhost:5000/
default_browser_return_url: http://localhost:25000/
allowed_return_urls:
- http://baron_backend:3000
- http://baron_backend:3000/
- http://localhost:5000
- http://backend:23000
- http://backend:23000/
- http://localhost:25000
- https://app.brsw.kr
- https://app.brsw.kr/
- https://sss.hmac.kr
@@ -44,6 +34,15 @@ selfservice:
- https://ssologin.hmac.kr/
- https://sso-test.hmac.kr
- https://sso-test.hmac.kr/
- https://ssob.hmac.kr
- https://ssob.hmac.kr/
- https://ssob.hmac.kr/ko
- https://ssob.hmac.kr/ko/
- https://ssob.hmac.kr/en
- https://ssob.hmac.kr/en/
- https://ssob.hmac.kr/auth/callback
- https://ssob.hmac.kr/ko/auth/callback
- https://ssob.hmac.kr/en/auth/callback
methods:
password:
@@ -56,24 +55,24 @@ selfservice:
flows:
error:
ui_url: http://localhost:5000/error
ui_url: http://localhost:25000/error
settings:
ui_url: http://localhost:5000/error?error=settings_disabled
ui_url: http://localhost:25000/error?error=settings_disabled
privileged_session_max_age: 15m
recovery:
ui_url: http://localhost:5000/recovery
ui_url: http://localhost:25000/recovery
use: code
verification:
ui_url: http://localhost:5000/verification
ui_url: http://localhost:25000/verification
use: code
logout:
after:
default_browser_return_url: http://localhost:5000/login
default_browser_return_url: http://localhost:25000/login
login:
ui_url: http://localhost:5000/login
ui_url: http://localhost:25000/login
lifespan: 10m
registration:
ui_url: http://localhost:5000/registration
ui_url: http://localhost:25000/registration
lifespan: 10m
log:
@@ -106,7 +105,7 @@ courier:
delivery_strategy: http
http:
request_config:
url: http://baron_backend:3000/api/v1/auth/webhooks/kratos-courier
url: http://backend:23000/api/v1/auth/webhooks/kratos-courier
method: POST
body: file:///etc/config/kratos/courier-http.jsonnet
headers:

View File

@@ -7,7 +7,7 @@
"methods": ["GET"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
@@ -21,7 +21,7 @@
"methods": ["OPTIONS"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
@@ -35,7 +35,7 @@
"methods": ["GET", "POST", "OPTIONS"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
@@ -49,7 +49,7 @@
"methods": ["POST", "PUT", "PATCH", "DELETE"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "cookie_session" }],
"authorizer": { "handler": "remote_json" },
@@ -63,7 +63,7 @@
"methods": ["GET"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "cookie_session" }],
"authorizer": { "handler": "remote_json" },
@@ -156,4 +156,4 @@
"authorizer": { "handler": "allow" },
"mutators": [{ "handler": "noop" }]
}
]
]

View File

@@ -7,7 +7,7 @@
"methods": ["GET"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
@@ -21,7 +21,7 @@
"methods": ["OPTIONS"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
@@ -35,7 +35,7 @@
"methods": ["GET", "POST", "OPTIONS"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "noop" }],
"authorizer": { "handler": "allow" },
@@ -49,7 +49,7 @@
"methods": ["POST", "PUT", "PATCH", "DELETE"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "cookie_session" }],
"authorizer": { "handler": "remote_json" },
@@ -63,7 +63,7 @@
"methods": ["GET"]
},
"upstream": {
"url": "http://baron_backend:3000"
"url": "http://backend:23000"
},
"authenticators": [{ "handler": "cookie_session" }],
"authorizer": { "handler": "remote_json" },

View File

@@ -3,7 +3,6 @@ map $time_iso8601 $time_custom {
"~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" "$1-$2-$3 $4:$5:$6";
}
# Go slog 포맷과 맞춘 JSON 액세스 로그
log_format json_combined escape=json
'{'
'"time":"$time_custom",'
@@ -29,18 +28,63 @@ server {
}
resolver 127.0.0.11 valid=10s ipv6=off;
set $backend_upstream http://baron_backend:3000;
set $userfront_upstream http://baron_userfront:5000;
set $oathkeeper_upstream http://ory_oathkeeper:4455;
set $backend_upstream http://backend:23000;
set $userfront_upstream http://userfront:5000;
set $oathkeeper_upstream http://oathkeeper:4455;
error_log /dev/stderr warn;
access_log /var/log/nginx/access.log json_combined;
# 안정성 튜닝
client_max_body_size 10m;
keepalive_timeout 65;
# --- CRITICAL: OIDC & OAuth2 (Must be at the TOP with ^~ to prevent falling through to /) ---
# Discovery Document
location ^~ /.well-known/openid-configuration {
proxy_pass $oathkeeper_upstream;
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;
}
# OAuth2 Auth/Token Endpoints (Standard)
location ^~ /oauth2/ {
proxy_pass $oathkeeper_upstream;
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;
}
# OAuth2 Auth/Token Endpoints (Localized - /ko/oauth2)
location ^~ /ko/oauth2/ {
rewrite ^/ko/oauth2/(.*)$ /oauth2/$1 break;
proxy_pass $oathkeeper_upstream;
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;
}
# OAuth2 Auth/Token Endpoints (Localized - /en/oauth2)
location ^~ /en/oauth2/ {
rewrite ^/en/oauth2/(.*)$ /oauth2/$1 break;
proxy_pass $oathkeeper_upstream;
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;
}
# OIDC Endpoints (Localized - /ko/oidc, /en/oidc)
location ^~ /ko/oidc/ {
rewrite ^/ko/oidc/(.*)$ /oidc/$1 last;
}
location ^~ /en/oidc/ {
rewrite ^/en/oidc/(.*)$ /oidc/$1 last;
}
# --- Other Services ---
# --- Backend API Proxy ---
location /api {
proxy_pass $backend_upstream;
proxy_set_header Host $host;
@@ -49,8 +93,6 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
}
# --- Ory Stack Proxy (via Oathkeeper) ---
# Kratos Public API
location /auth {
proxy_pass $oathkeeper_upstream;
proxy_set_header Host $host;
@@ -59,7 +101,6 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
}
# Hydra Public API
location /oidc {
rewrite ^/oidc/(.*)$ /$1 break;
proxy_pass $oathkeeper_upstream;
@@ -69,36 +110,7 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
}
# --- 내부 웹앱 프록시 (초기에는 Private Net 내부에서만 운영) ---
# AdminFront (Vite Dev Server or Nginx)
# location /admin {
# proxy_pass http://baron_adminfront:5173;
# 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;
#
# # WebSocket 지원 (Vite HMR)
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# }
# DevFront (Vite Dev Server or Nginx)
# location /dev {
# proxy_pass http://baron_devfront:5173;
# 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;
#
# # WebSocket 지원 (Vite HMR)
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# }
# --- UserFront 정적 파일 프록시 ---
# --- Default: UserFront ---
location / {
proxy_pass $userfront_upstream;
proxy_set_header Host $host;

View File

@@ -726,6 +726,7 @@ body = "We could not find an account for that information.\\\\\\\\\\\\\\\\nPleas
[msg.userfront.login.verification]
approved = "Approved. Complete sign-in in the original window."
approved_local = "Approved. This device is already signed in, and the remote window will be signed in shortly."
approved_remote = "Approved. Please return to your original browser or PC screen."
success = "Sign-in approval completed."
[msg.userfront.login_success]
@@ -2287,8 +2288,10 @@ title = "Account not found"
[ui.userfront.login.verification]
action_label = "Done"
action_label_close = "Close Window"
page_title = "Sign-in approval"
title = "Approval complete"
title_remote = "Sign-in approval complete"
[ui.userfront.login_success]
later = "Do this later (go to dashboard)"

View File

@@ -1164,6 +1164,7 @@ body = "가입되지 않은 정보입니다.\\\\n회원가입 후 이용해 주
[msg.userfront.login.verification]
approved = "승인되었습니다. 로그인은 요청하신 창에서 완료됩니다."
approved_local = "승인 되었습니다. 이 기기는 로그인되어 있는 상태입니다. 원격 창도 로그인이 될 예정입니다"
approved_remote = "승인되었습니다. 요청하신 브라우저 또는 PC 화면으로 돌아가 주세요."
success = "로그인 승인에 성공했습니다."
[msg.userfront.login_success]
@@ -2686,8 +2687,10 @@ title = "미등록 회원"
[ui.userfront.login.verification]
action_label = "확인"
action_label_close = "창 닫기"
page_title = "로그인 승인"
title = "승인 완료"
title_remote = "로그인 승인 완료"
[ui.userfront.login_success]
later = "나중에 하기 (대시보드로 이동)"

View File

@@ -1039,6 +1039,7 @@ body = ""
[msg.userfront.login.verification]
approved = ""
approved_local = ""
approved_remote = ""
success = ""
[msg.userfront.login_success]
@@ -2561,8 +2562,10 @@ title = ""
[ui.userfront.login.verification]
action_label = ""
action_label_close = ""
page_title = ""
title = ""
title_remote = ""
[ui.userfront.login_success]
later = ""

95
scripts/setup_test.sh Normal file
View File

@@ -0,0 +1,95 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env.test2"
AUTH_CONFIG_TEST2="$ROOT_DIR/.generated/auth-config.test2.env"
echo "Creating clean $ENV_FILE..."
if [[ -f "$ROOT_DIR/.env.sample" ]]; then
grep -vE "PORT|URL|CALLBACK|ALLOWED_RETURN" "$ROOT_DIR/.env.sample" > "$ENV_FILE"
else
touch "$ENV_FILE"
fi
cat >> "$ENV_FILE" <<EOF
APP_ENV=development
DB_PORT=25432
CLICKHOUSE_PORT_HTTP=28123
CLICKHOUSE_PORT_NATIVE=29000
BACKEND_PORT=23000
ADMINFRONT_PORT=25173
DEVFRONT_PORT=25174
USERFRONT_PORT=25000
REDIS_PORT=26399
OATHKEEPER_PROXY_PORT=24467
USERFRONT_URL=https://ssob.hmac.kr
ADMINFRONT_URL=https://adminb.hmac.kr
DEVFRONT_URL=https://devb.hmac.kr
ADMINFRONT_CALLBACK_URLS=https://adminb.hmac.kr/auth/callback
DEVFRONT_CALLBACK_URLS=https://devb.hmac.kr/auth/callback
KRATOS_UI_URL=https://ssob.hmac.kr/auth
KRATOS_BROWSER_URL=https://ssob.hmac.kr/auth
# Explicitly define for auth_config.sh
HYDRA_PUBLIC_URL=https://ssob.hmac.kr/oidc
OATHKEEPER_PUBLIC_URL=https://ssob.hmac.kr
EOF
if [[ -f "$ROOT_DIR/.env" ]]; then
grep -E "SECRET|KEY|PASSWORD|ID|SENDER|SES|AWS|ADMIN_EMAIL|ADMIN_PASSWORD" "$ROOT_DIR/.env" >> "$ENV_FILE" || true
fi
# Ensure mandatory secrets exist for test2
grep -q "COOKIE_SECRET=" "$ENV_FILE" || echo "COOKIE_SECRET=test2_cookie_secret_12345678" >> "$ENV_FILE"
grep -q "JWT_SECRET=" "$ENV_FILE" || echo "JWT_SECRET=test2_jwt_secret_12345678" >> "$ENV_FILE"
echo "Generating auth config for test2..."
(
if [[ -f "$ROOT_DIR/.env" ]]; then mv "$ROOT_DIR/.env" "$ROOT_DIR/.env.tmp"; fi
cp "$ENV_FILE" "$ROOT_DIR/.env"
bash "$ROOT_DIR/scripts/auth_config.sh" build
cp "$ROOT_DIR/.generated/auth-config.env" "$AUTH_CONFIG_TEST2"
rm "$ROOT_DIR/.env"
if [[ -f "$ROOT_DIR/.env.tmp" ]]; then mv "$ROOT_DIR/.env.tmp" "$ROOT_DIR/.env"; fi
)
echo "Starting test2 stack..."
docker compose -p test2 down --remove-orphans || true
export DB_PORT=25432
export CLICKHOUSE_PORT_HTTP=28123
export CLICKHOUSE_PORT_NATIVE=29000
export BACKEND_PORT=23000
export ADMINFRONT_PORT=25173
export DEVFRONT_PORT=25174
export USERFRONT_PORT=25000
export REDIS_PORT=26399
export OATHKEEPER_PROXY_PORT=24467
# 1. Start DBs first
docker compose -p test2 -f compose.infra.test2.yaml -f compose.ory.test2.yaml up -d postgres postgres_ory redis
echo "Waiting for DBs to be healthy..."
sleep 15
# 2. Force create databases
echo "Creating databases if they don't exist..."
docker exec test2-postgres-1 psql -U baron -d postgres -c "CREATE DATABASE baron_sso;" || echo "DB baron_sso might already exist"
docker exec test2-postgres_ory-1 psql -U ory -d postgres -c "CREATE DATABASE ory_kratos;" || echo "DB ory_kratos might already exist"
docker exec test2-postgres_ory-1 psql -U ory -d postgres -c "CREATE DATABASE ory_hydra;" || echo "DB ory_hydra might already exist"
docker exec test2-postgres_ory-1 psql -U ory -d postgres -c "CREATE DATABASE ory_keto;" || echo "DB ory_keto might already exist"
# 3. Start everything else
docker compose -p test2 \
-f compose.infra.test2.yaml \
-f compose.ory.test2.yaml \
-f docker-compose.test2.yaml \
--env-file .env.test2 \
up -d --build
echo "test2 stack is up!"
echo "UserFront: http://localhost:25000"
echo "AdminFront: http://localhost:25173"
echo "DevFront: http://localhost:25174"

100
scripts/setup_test2.sh Executable file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env.test2"
AUTH_CONFIG_TEST2="$ROOT_DIR/.generated/auth-config.test2.env"
echo "Creating clean $ENV_FILE..."
if [[ -f "$ROOT_DIR/.env.sample" ]]; then
grep -vE "PORT|URL|CALLBACK|ALLOWED_RETURN" "$ROOT_DIR/.env.sample" > "$ENV_FILE"
else
touch "$ENV_FILE"
fi
cat >> "$ENV_FILE" <<EOF
APP_ENV=development
DB_PORT=25432
CLICKHOUSE_PORT_HTTP=28123
CLICKHOUSE_PORT_NATIVE=29000
BACKEND_PORT=23000
ADMINFRONT_PORT=25173
DEVFRONT_PORT=25174
USERFRONT_PORT=25000
REDIS_PORT=26399
OATHKEEPER_PROXY_PORT=24467
USERFRONT_URL=https://ssob.hmac.kr
ADMINFRONT_URL=https://adminb.hmac.kr
DEVFRONT_URL=https://devb.hmac.kr
ADMINFRONT_CALLBACK_URLS=https://adminb.hmac.kr/auth/callback
DEVFRONT_CALLBACK_URLS=https://devb.hmac.kr/auth/callback
KRATOS_UI_URL=https://ssob.hmac.kr/auth
KRATOS_BROWSER_URL=https://ssob.hmac.kr/auth
# Explicitly define for auth_config.sh
HYDRA_PUBLIC_URL=https://ssob.hmac.kr/oidc
OATHKEEPER_PUBLIC_URL=https://ssob.hmac.kr
EOF
if [[ -f "$ROOT_DIR/.env" ]]; then
grep -E "SECRET|KEY|PASSWORD|ID|SENDER|SES|AWS|ADMIN_EMAIL|ADMIN_PASSWORD" "$ROOT_DIR/.env" >> "$ENV_FILE" || true
fi
# Ensure mandatory secrets exist for test2
grep -q "COOKIE_SECRET=" "$ENV_FILE" || echo "COOKIE_SECRET=test2_cookie_secret_12345678" >> "$ENV_FILE"
grep -q "JWT_SECRET=" "$ENV_FILE" || echo "JWT_SECRET=test2_jwt_secret_12345678" >> "$ENV_FILE"
echo "Generating auth config for test2..."
(
if [[ -f "$ROOT_DIR/.env" ]]; then mv "$ROOT_DIR/.env" "$ROOT_DIR/.env.tmp"; fi
cp "$ENV_FILE" "$ROOT_DIR/.env"
bash "$ROOT_DIR/scripts/auth_config.sh" build
cp "$ROOT_DIR/.generated/auth-config.env" "$AUTH_CONFIG_TEST2"
rm "$ROOT_DIR/.env"
if [[ -f "$ROOT_DIR/.env.tmp" ]]; then mv "$ROOT_DIR/.env.tmp" "$ROOT_DIR/.env"; fi
)
echo "Starting test2 stack..."
docker compose -p test2 down --remove-orphans || true
# Export variables for docker-compose substitution
export DB_PORT=25432
export CLICKHOUSE_PORT_HTTP=28123
export CLICKHOUSE_PORT_NATIVE=29000
export BACKEND_PORT=23000
export ADMINFRONT_PORT=25173
export DEVFRONT_PORT=25174
export USERFRONT_PORT=25000
export REDIS_PORT=26399
export OATHKEEPER_PROXY_PORT=24467
# Load generated auth config variables
if [[ -f "$AUTH_CONFIG_TEST2" ]]; then
export $(grep -v '^#' "$AUTH_CONFIG_TEST2" | xargs)
fi
# 1. Start DBs first
docker compose -p test2 -f compose.infra.test2.yaml -f compose.ory.test2.yaml up -d postgres postgres_ory redis
echo "Waiting for DBs to be healthy..."
sleep 15
# 2. Force create databases
echo "Creating databases if they don't exist..."
docker exec test2-postgres-1 psql -U baron -d postgres -c "CREATE DATABASE baron_sso;" || echo "DB baron_sso might already exist"
docker exec test2-postgres_ory-1 psql -U ory -d postgres -c "CREATE DATABASE ory_kratos;" || echo "DB ory_kratos might already exist"
docker exec test2-postgres_ory-1 psql -U ory -d postgres -c "CREATE DATABASE ory_hydra;" || echo "DB ory_hydra might already exist"
docker exec test2-postgres_ory-1 psql -U ory -d postgres -c "CREATE DATABASE ory_keto;" || echo "DB ory_keto might already exist"
# 3. Start everything else
docker compose -p test2 \
-f compose.infra.test2.yaml \
-f compose.ory.test2.yaml \
-f docker-compose.test2.yaml \
up -d --build
echo "test2 stack is up!"
echo "UserFront: http://localhost:25000"
echo "AdminFront: http://localhost:25173"
echo "DevFront: http://localhost:25174"

View File

@@ -213,6 +213,7 @@ body = "We could not find an account for that information.\\\\\\\\\\\\\\\\nPleas
[msg.userfront.login.verification]
approved = "Approved. Complete sign-in in the original window."
approved_local = "Approved. This device is already signed in, and the remote window will be signed in shortly."
approved_remote = "Approved. Please return to your original browser or PC screen."
success = "Sign-in approval completed."
[msg.userfront.login_success]
@@ -554,8 +555,10 @@ title = "Account not found"
[ui.userfront.login.verification]
action_label = "Done"
action_label_close = "Close Window"
page_title = "Sign-in approval"
title = "Approval complete"
title_remote = "Sign-in approval complete"
[ui.userfront.login_success]
later = "Do this later (go to dashboard)"

View File

@@ -420,6 +420,7 @@ body = "가입되지 않은 정보입니다.\\\\n회원가입 후 이용해 주
[msg.userfront.login.verification]
approved = "승인되었습니다. 로그인은 요청하신 창에서 완료됩니다."
approved_local = "승인 되었습니다. 이 기기는 로그인되어 있는 상태입니다. 원격 창도 로그인이 될 예정입니다"
approved_remote = "승인되었습니다. 요청하신 브라우저 또는 PC 화면으로 돌아가 주세요."
success = "로그인 승인에 성공했습니다."
[msg.userfront.login_success]
@@ -759,8 +760,10 @@ title = "미등록 회원"
[ui.userfront.login.verification]
action_label = "확인"
action_label_close = "창 닫기"
page_title = "로그인 승인"
title = "승인 완료"
title_remote = "로그인 승인 완료"
[ui.userfront.login_success]
later = "나중에 하기 (대시보드로 이동)"

View File

@@ -392,6 +392,7 @@ body = ""
[msg.userfront.login.verification]
approved = ""
approved_local = ""
approved_remote = ""
success = ""
[msg.userfront.login_success]
@@ -731,8 +732,10 @@ title = ""
[ui.userfront.login.verification]
action_label = ""
action_label_close = ""
page_title = ""
title = ""
title_remote = ""
[ui.userfront.login_success]
later = ""

View File

@@ -722,6 +722,14 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
return false;
}
bool _isAlreadyVerifiedError(Object e) {
final msg = e.toString().toLowerCase();
return msg.contains('already_used') ||
msg.contains('already_verified') ||
msg.contains('session_active') ||
msg.contains('verify_failed');
}
void _markVerificationApproved(
String message, {
String? title,
@@ -730,13 +738,19 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
String actionPath = '/',
bool autoRedirect = false,
Duration redirectDelay = const Duration(seconds: 2),
bool isRemote = false,
}) {
if (!mounted) return;
final resolvedTitle = title ?? tr('ui.userfront.login.verification.title');
final resolvedTitle = title ??
(isRemote
? tr('ui.userfront.login.verification.title_remote')
: tr('ui.userfront.login.verification.title'));
final resolvedPageTitle =
pageTitle ?? tr('ui.userfront.login.verification.page_title');
final resolvedActionLabel =
actionLabel ?? tr('ui.userfront.login.verification.action_label');
final resolvedActionLabel = actionLabel ??
(isRemote
? tr('ui.userfront.login.verification.action_label_close')
: tr('ui.userfront.login.verification.action_label'));
setState(() {
_verificationApproved = true;
_verificationMessage = message;
@@ -744,6 +758,10 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
_verificationPageTitle = resolvedPageTitle;
_verificationActionLabel = resolvedActionLabel;
});
if (isRemote) {
_verificationRedirectTimer?.cancel();
return;
}
_verificationRedirectTimer?.cancel();
if (autoRedirect) {
_verificationRedirectTimer = Timer(redirectDelay, () {
@@ -785,6 +803,11 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
const SizedBox(height: 24),
FilledButton(
onPressed: () {
if (_verificationActionLabel ==
tr('ui.userfront.login.verification.action_label_close')) {
webWindow.close();
return;
}
final hasLocalSession =
(AuthTokenStore.getToken()?.isNotEmpty ?? false) ||
AuthTokenStore.usesCookie();
@@ -813,6 +836,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
final localSessionMessage = tr(
'msg.userfront.login.verification.approved_local',
);
final remoteApprovedMessage =
tr('msg.userfront.login.verification.approved_remote');
try {
// Use Backend to verify the token (Backend-Driven Flow)
final res = await AuthProxyService.verifyMagicLink(
@@ -829,7 +855,11 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
if (status == 'approved' || (jwt == null && _verificationOnly)) {
if (mounted) {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
if (_verificationOnly && !hasLocalSession) {
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
} else {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
}
}
return;
}
@@ -847,15 +877,23 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
}
if (mounted) {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
if (_verificationOnly && !hasLocalSession) {
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
} else {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
}
}
} catch (e) {
debugPrint("[Auth] Verification FAILED for token: $token. Error: $e");
if (mounted) {
if (_verificationOnly && _isAlreadyVerifiedError(e)) {
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
return;
}
_showError(
tr(
'msg.userfront.login.verification_failed',
params: {'error': e.toString()},
params: {'error': e.toString().replaceFirst('Exception: ', '')},
),
);
}
@@ -875,6 +913,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
final localSessionMessage = tr(
'msg.userfront.login.verification.approved_local',
);
final remoteApprovedMessage =
tr('msg.userfront.login.verification.approved_remote');
try {
final res = await AuthProxyService.verifyLoginCode(
sanitizedLoginId,
@@ -894,7 +935,11 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
if (jwt == null && status == 'approved') {
if (mounted) {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
if (_verificationOnly && !hasLocalSession) {
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
} else {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
}
}
return;
}
@@ -908,7 +953,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
return;
}
if (_verificationOnly) {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
return;
}
_onLoginSuccess(jwt, provider: res['provider'] as String?);
@@ -916,17 +961,25 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
}
if (_verificationOnly && mounted) {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
if (!hasLocalSession) {
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
} else {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
}
}
} catch (e) {
debugPrint(
"[Auth] Code verification FAILED for loginId: $sanitizedLoginId. Error: $e",
);
if (mounted) {
if (_verificationOnly && _isAlreadyVerifiedError(e)) {
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
return;
}
_showError(
tr(
'msg.userfront.login.verification_failed',
params: {'error': e.toString()},
params: {'error': e.toString().replaceFirst('Exception: ', '')},
),
);
}
@@ -941,6 +994,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
final localSessionMessage = tr(
'msg.userfront.login.verification.approved_local',
);
final remoteApprovedMessage =
tr('msg.userfront.login.verification.approved_remote');
try {
final res = await AuthProxyService.verifyLoginShortCode(
sanitized,
@@ -956,7 +1012,11 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
if (jwt == null && status == 'approved') {
if (mounted) {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
if (_verificationOnly && !hasLocalSession) {
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
} else {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
}
}
return;
}
@@ -970,7 +1030,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
return;
}
if (_verificationOnly) {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
return;
}
_onLoginSuccess(jwt, provider: res['provider'] as String?);
@@ -978,15 +1038,23 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
}
if (_verificationOnly && mounted) {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
if (!hasLocalSession) {
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
} else {
_markVerificationApproved(approvedMessage, actionPath: actionPath);
}
}
} catch (e) {
debugPrint("[Auth] Short code verification FAILED. Error: $e");
if (mounted) {
if (_verificationOnly && _isAlreadyVerifiedError(e)) {
_markVerificationApproved(remoteApprovedMessage, isRemote: true);
return;
}
_showError(
tr(
'msg.userfront.login.verification_failed',
params: {'error': e.toString()},
params: {'error': e.toString().replaceFirst('Exception: ', '')},
),
);
}

File diff suppressed because one or more lines are too long

View File

@@ -32,7 +32,7 @@ server {
# --- Backend API Proxy ---
location /api {
proxy_pass http://baron_backend:3000;
proxy_pass http://backend:23000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;