forked from baron/baron-sso
한 endpoint URL로 전체 서빙 #120
This commit is contained in:
38
.env.sample
38
.env.sample
@@ -57,17 +57,19 @@ ADMIN_EMAIL=admin@baron.co.kr
|
||||
ADMIN_PASSWORD=adminPasswordIsNotSimple
|
||||
|
||||
# --- URLs for Proxy/Handoff ---
|
||||
USERFRONT_URL=https://sso.hmac.kr # 프론트엔드 접속 주소 (이메일/SMS 링크 생성 시 사용)
|
||||
BACKEND_URL=https://sso.hmac.kr # 프론트엔드에서 참조할 백엔드 API 주소
|
||||
|
||||
# Project Public Base URL (Served by UserFront Nginx)
|
||||
USERFRONT_URL=https://sso.hmac.kr
|
||||
|
||||
# Services proxied via Nginx
|
||||
BACKEND_URL=${USERFRONT_URL}/api
|
||||
OATHKEEPER_PUBLIC_URL=${USERFRONT_URL}
|
||||
|
||||
# ory-stack 변수들
|
||||
ORY_POSTGRES_TAG=17-trixie
|
||||
ORY_POSTGRES_USER=ory
|
||||
ORY_POSTGRES_PASSWORD=EuBV5ywvXFehkggHQrnYo5727MseEi6i9
|
||||
ORY_POSTGRES_DB=ory
|
||||
ORY_POSTGRES_PORT=5433
|
||||
# ORY_POSTGRES_PORT=5433 # Internal only
|
||||
|
||||
KRATOS_DB=ory_kratos
|
||||
HYDRA_DB=ory_hydra
|
||||
@@ -75,31 +77,39 @@ KETO_DB=ory_keto
|
||||
|
||||
# Ory Kratos Configuration
|
||||
KRATOS_VERSION=v25.4.0-distroless
|
||||
KRATOS_PUBLIC_PORT=4433
|
||||
KRATOS_ADMINFRONT_PORT=4434
|
||||
# KRATOS_PUBLIC_PORT=4433 # Internal only
|
||||
# KRATOS_ADMINFRONT_PORT=4434 # Internal only
|
||||
|
||||
KRATOS_UI_NODE_VERSION=v25.4.0
|
||||
KRATOS_UI_PORT=4455
|
||||
# KRATOS_UI_PORT=4455 # Internal only
|
||||
|
||||
# Ory Hydra Configuration
|
||||
HYDRA_VERSION=v25.4.0-distroless
|
||||
HYDRA_PUBLIC_PORT=4441
|
||||
HYDRA_ADMINFRONT_PORT=4445
|
||||
# HYDRA_PUBLIC_PORT=4441 # Internal only
|
||||
# HYDRA_ADMINFRONT_PORT=4445 # Internal only
|
||||
|
||||
# Ory Keto Configuration
|
||||
KETO_VERSION=v25.4.0-distroless
|
||||
KETO_READ_PORT=4466
|
||||
KETO_WRITE_PORT=4467
|
||||
# KETO_READ_PORT=4466 # Internal only
|
||||
# KETO_WRITE_PORT=4467 # Internal only
|
||||
|
||||
# Kratos Selfservice UI upstreams (override for deployments)
|
||||
ORY_SDK_URL=http://kratos:4433
|
||||
KRATOS_PUBLIC_URL=http://kratos:4433
|
||||
KRATOS_ADMIN_URL=http://kratos:4434
|
||||
# 브라우저가 접근할 Kratos Public/UI 외부 URL (리버스 프록시/도메인 환경 고려)
|
||||
KRATOS_BROWSER_URL=http://localhost:4433
|
||||
|
||||
# 브라우저가 접근할 Kratos Public/UI 외부 URL
|
||||
# Oathkeeper가 /auth 경로를 Kratos Public API로 라우팅합니다.
|
||||
KRATOS_BROWSER_URL=${OATHKEEPER_PUBLIC_URL}/auth
|
||||
# Kratos UI는 별도 서브도메인이 없으면 UserFront가 렌더링하거나 /kratos-ui 등으로 라우팅 필요
|
||||
# 현재는 예시로 로컬 포트 유지 (프로덕션에선 UserFront에 통합됨)
|
||||
KRATOS_UI_URL=http://localhost:4455
|
||||
|
||||
HYDRA_ADMIN_URL=http://hydra:4445
|
||||
HYDRA_PUBLIC_URL=http://hydra:4444
|
||||
# Oathkeeper가 /oidc 경로를 Hydra Public API로 라우팅합니다.
|
||||
HYDRA_PUBLIC_URL=${OATHKEEPER_PUBLIC_URL}/oidc
|
||||
|
||||
# Oathkeeper JWKS (내부 통신용)
|
||||
JWKS_URL=http://oathkeeper:4456/.well-known/jwks.json
|
||||
|
||||
# Oathkeeper 실행 사용자/프로브 설정
|
||||
|
||||
@@ -176,6 +176,7 @@ Ory Stack과 애플리케이션 간 통신을 위한 도커 네트워크를 생
|
||||
docker network create -d bridge ory-net
|
||||
docker network create hydranet
|
||||
docker network create kratosnet
|
||||
docker network create public_net #서비스용
|
||||
```
|
||||
|
||||
#### 2. 인프라 및 Ory Stack 실행
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/descope/go-sdk/descope"
|
||||
"github.com/descope/go-sdk/descope/client"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
@@ -1357,7 +1358,6 @@ func (h *AuthHandler) CompletePasswordReset(c *fiber.Ctx) error {
|
||||
// InitQRLogin - Step 1: Web 패널에서 QR 로그인 세션을 생성합니다.
|
||||
func (h *AuthHandler) InitQRLogin(c *fiber.Ctx) error {
|
||||
pendingRef := GenerateSecureToken(16)
|
||||
userCode := GenerateUserCode()
|
||||
|
||||
// QR 코드 페이로드를 실제 접속 가능한 URL로 변경합니다.
|
||||
userfrontURL := os.Getenv("USERFRONT_URL")
|
||||
@@ -1376,7 +1376,6 @@ func (h *AuthHandler) InitQRLogin(c *fiber.Ctx) error {
|
||||
"pendingRef": pendingRef,
|
||||
"expiresIn": 300,
|
||||
"interval": int(minPollInterval.Seconds()),
|
||||
"userCode": userCode,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1433,17 +1432,26 @@ func (h *AuthHandler) ScanQRLogin(c *fiber.Ctx) error {
|
||||
|
||||
slog.Info("[QR] Scan & Approve", "pendingRef", req.PendingRef)
|
||||
|
||||
if req.Token == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Missing session token"})
|
||||
}
|
||||
|
||||
// 1. Redis에서 세션 확인
|
||||
val, err := h.RedisService.Get(prefixSession + req.PendingRef)
|
||||
if err != nil || val == "" {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Session expired or not found"})
|
||||
}
|
||||
|
||||
// 2. 모바일 유저의 토큰으로 새 세션 토큰(웹용)을 발행하거나 그대로 전달
|
||||
// 2. 모바일 토큰은 승인 검증용으로만 사용하고, 웹 전용 세션을 새로 발급
|
||||
sessionToken, err := h.issueQRWebSession(c, req.Token)
|
||||
if err != nil {
|
||||
slog.Error("[QR] Issue web session failed", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to issue web session"})
|
||||
}
|
||||
|
||||
sessionData, _ := json.Marshal(map[string]string{
|
||||
"status": statusSuccess,
|
||||
"jwt": req.Token,
|
||||
"jwt": sessionToken,
|
||||
})
|
||||
h.RedisService.Set(prefixSession+req.PendingRef, string(sessionData), 5*time.Minute)
|
||||
|
||||
@@ -1910,6 +1918,108 @@ func (h *AuthHandler) resolveIdentityID(c *fiber.Ctx, token string) (string, err
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (h *AuthHandler) issueQRWebSession(c *fiber.Ctx, token string) (string, error) {
|
||||
if looksLikeJWT(token) && h.DescopeClient != nil {
|
||||
authorized, userToken, err := h.DescopeClient.Auth.ValidateSessionWithToken(c.Context(), token)
|
||||
if err == nil && authorized {
|
||||
loginID, err := h.resolveDescopeLoginID(c.Context(), userToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
authInfo, err := h.IdpProvider.IssueSession(loginID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if authInfo == nil || authInfo.SessionToken == nil || authInfo.SessionToken.JWT == "" {
|
||||
return "", fmt.Errorf("descope issue session returned empty token")
|
||||
}
|
||||
return authInfo.SessionToken.JWT, nil
|
||||
}
|
||||
}
|
||||
|
||||
identityID, _, err := h.getKratosIdentity(token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return h.issueKratosSession(c.Context(), identityID)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) resolveDescopeLoginID(ctx context.Context, token *descope.Token) (string, error) {
|
||||
if token == nil {
|
||||
return "", fmt.Errorf("descope token is nil")
|
||||
}
|
||||
|
||||
if loginID := extractLoginIDFromClaims(token.Claims); loginID != "" {
|
||||
return loginID, nil
|
||||
}
|
||||
|
||||
if h.DescopeClient == nil {
|
||||
return "", fmt.Errorf("descope client is nil")
|
||||
}
|
||||
|
||||
user, err := h.DescopeClient.Management.User().Load(ctx, token.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if user == nil {
|
||||
return "", fmt.Errorf("descope user not found")
|
||||
}
|
||||
if loginID := pickPrimaryLoginID(user.LoginIDs); loginID != "" {
|
||||
return loginID, nil
|
||||
}
|
||||
if user.Email != "" {
|
||||
return user.Email, nil
|
||||
}
|
||||
if user.Phone != "" {
|
||||
return user.Phone, nil
|
||||
}
|
||||
return "", fmt.Errorf("descope login id not found")
|
||||
}
|
||||
|
||||
func pickPrimaryLoginID(loginIDs []string) string {
|
||||
for _, id := range loginIDs {
|
||||
if strings.Contains(id, "@") {
|
||||
return id
|
||||
}
|
||||
}
|
||||
if len(loginIDs) > 0 {
|
||||
return loginIDs[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractLoginIDFromClaims(claims map[string]any) string {
|
||||
if claims == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
candidateKeys := []string{"loginId", "login_id", "email", "phone_number", "phone", "phoneNumber"}
|
||||
for _, key := range candidateKeys {
|
||||
if raw, ok := claims[key]; ok {
|
||||
if value, ok := raw.(string); ok && value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if raw, ok := claims["loginIds"]; ok {
|
||||
switch ids := raw.(type) {
|
||||
case []string:
|
||||
return pickPrimaryLoginID(ids)
|
||||
case []any:
|
||||
casted := make([]string, 0, len(ids))
|
||||
for _, item := range ids {
|
||||
if value, ok := item.(string); ok && value != "" {
|
||||
casted = append(casted, value)
|
||||
}
|
||||
}
|
||||
return pickPrimaryLoginID(casted)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *AuthHandler) getKratosIdentity(sessionToken string) (string, map[string]interface{}, error) {
|
||||
kratosURL := strings.TrimRight(os.Getenv("KRATOS_PUBLIC_URL"), "/")
|
||||
if kratosURL == "" {
|
||||
@@ -1944,6 +2054,49 @@ func (h *AuthHandler) getKratosIdentity(sessionToken string) (string, map[string
|
||||
return result.Identity.ID, result.Identity.Traits, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) issueKratosSession(ctx context.Context, identityID string) (string, error) {
|
||||
if identityID == "" {
|
||||
return "", fmt.Errorf("kratos identity id is empty")
|
||||
}
|
||||
|
||||
kratosAdminURL := strings.TrimRight(os.Getenv("KRATOS_ADMIN_URL"), "/")
|
||||
if kratosAdminURL == "" {
|
||||
kratosAdminURL = "http://kratos:4434"
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"identity_id": identityID,
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, kratosAdminURL+"/admin/sessions", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||
if resp.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("kratos admin create session failed status=%d body=%s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
var parsed struct {
|
||||
SessionToken string `json:"session_token"`
|
||||
}
|
||||
if err := json.Unmarshal(respBody, &parsed); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if parsed.SessionToken == "" {
|
||||
return "", fmt.Errorf("kratos admin session token missing: %s", string(respBody))
|
||||
}
|
||||
return parsed.SessionToken, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) getKratosIdentityWithCookie(cookie string) (string, map[string]interface{}, error) {
|
||||
kratosURL := strings.TrimRight(os.Getenv("KRATOS_PUBLIC_URL"), "/")
|
||||
if kratosURL == "" {
|
||||
|
||||
@@ -162,6 +162,7 @@ services:
|
||||
entrypoint: ["/etc/config/oathkeeper/entrypoint.sh"]
|
||||
networks:
|
||||
- ory-net
|
||||
- public_net
|
||||
|
||||
ory_clickhouse:
|
||||
image: clickhouse/clickhouse-server:latest
|
||||
@@ -251,3 +252,6 @@ networks:
|
||||
kratosnet:
|
||||
external: true
|
||||
name: kratosnet
|
||||
public_net:
|
||||
external: true
|
||||
name: public_net
|
||||
|
||||
@@ -80,7 +80,6 @@ services:
|
||||
- /app/node_modules
|
||||
networks:
|
||||
- baron_net
|
||||
|
||||
userfront:
|
||||
build:
|
||||
context: ./userfront
|
||||
@@ -97,6 +96,8 @@ services:
|
||||
- "${USERFRONT_PORT:-5000}:5000"
|
||||
networks:
|
||||
- baron_net
|
||||
- public_net
|
||||
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
@@ -126,3 +127,6 @@ networks:
|
||||
ory-net:
|
||||
external: true
|
||||
name: ory-net
|
||||
public_net:
|
||||
external: true
|
||||
name: public_net
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
### 2.3 QR 로그인
|
||||
1. `POST /api/v1/auth/qr/init` → `qrCode`, `pendingRef` 수신
|
||||
2. 웹은 `POST /api/v1/auth/qr/poll`로 폴링
|
||||
3. 모바일 앱은 `POST /api/v1/auth/qr/approve`로 승인
|
||||
3. 모바일 앱은 `POST /api/v1/auth/qr/approve`로 승인 (모바일 세션 토큰은 승인 검증용)
|
||||
4. Polling 응답에서 `sessionJwt` 수신
|
||||
|
||||
### 2.4 SMS 코드 로그인
|
||||
@@ -71,7 +71,7 @@
|
||||
- **ID/Password 로그인**: IDP 추상화 사용 (Ory/Descope) — 정상
|
||||
- **Enchanted/Magic Link**: 현재는 Descope 기반 로직이 포함됨. Ory 전환 시 Kratos `code/link` 플로우로 교체 필요
|
||||
- **SMS 코드**: 내부 토큰(placeholder). Kratos 세션 교환 로직 추가 필요
|
||||
- **QR 로그인**: 모바일 세션 토큰을 웹 세션으로 전달. Ory일 경우 Kratos 세션 토큰을 전달하도록 UI/토큰 저장 방식 정비 필요
|
||||
- **QR 로그인**: 모바일 세션 토큰은 승인 검증용으로만 사용하고, 백엔드에서 웹 전용 세션을 새로 발급
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# ==========================================
|
||||
# Baron SSO - Unified Environment Configuration
|
||||
# ==========================================
|
||||
|
||||
# --- General System ---
|
||||
APP_ENV=development
|
||||
TZ=Asia/Seoul
|
||||
|
||||
# --- Infrastructure Ports ---
|
||||
DB_PORT=5432
|
||||
CLICKHOUSE_PORT_HTTP=8123
|
||||
CLICKHOUSE_PORT_NATIVE=9000
|
||||
BACKEND_PORT=3000
|
||||
USERFRONT_PORT=5000
|
||||
|
||||
# --- Database Credentials (PostgreSQL) ---
|
||||
DB_USER=baron
|
||||
DB_PASSWORD=password
|
||||
DB_NAME=baron_sso
|
||||
|
||||
# --- Backend Configuration ---
|
||||
# Must be 32 bytes. Generate with `openssl rand -hex 32`
|
||||
COOKIE_SECRET=super-secret-key-must-be-32-bytes!
|
||||
REDIS_ADDR=redis:6379
|
||||
|
||||
# --- Frontend Configuration ---
|
||||
# Descope Project ID (Required for Auth)
|
||||
DESCOPE_PROJECT_ID=P2t...your_descope_project_id
|
||||
|
||||
# --- Naver Cloud Services ---
|
||||
NAVER_CLOUD_ACCESS_KEY=ncp_iam_...
|
||||
NAVER_CLOUD_SECRET_KEY=ncp_iam_...
|
||||
NAVER_CLOUD_SERVICE_ID=ncp:sms:kr:...:...
|
||||
NAVER_SENDER_PHONE_NUMBER=...
|
||||
@@ -1,5 +1,6 @@
|
||||
# Stage 1: Build Flutter
|
||||
FROM ghcr.io/cirruslabs/flutter:stable AS build
|
||||
ENV RUN_FLUTTER_AS_ROOT=true
|
||||
# ENV RUN_FLUTTER_AS_ROOT=true
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:descope/descope.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../core/services/auth_proxy_service.dart';
|
||||
import '../../../../core/services/auth_token_store.dart';
|
||||
|
||||
class ApproveQrScreen extends StatefulWidget {
|
||||
final String? pendingRef;
|
||||
@@ -19,8 +20,9 @@ class _ApproveQrScreenState extends State<ApproveQrScreen> {
|
||||
Future<void> _handleApprove() async {
|
||||
if (widget.pendingRef == null) return;
|
||||
|
||||
final storedToken = AuthTokenStore.getToken();
|
||||
final session = Descope.sessionManager.session;
|
||||
if (session == null || session.refreshToken.isExpired) {
|
||||
if (storedToken == null && (session == null || session.refreshToken.isExpired)) {
|
||||
setState(() => _message = "Please log in on your phone first.");
|
||||
context.go('/signin'); // Redirect to login
|
||||
return;
|
||||
@@ -32,9 +34,10 @@ class _ApproveQrScreenState extends State<ApproveQrScreen> {
|
||||
});
|
||||
// jwt 유효성 확인
|
||||
try {
|
||||
final token = storedToken ?? session?.sessionToken.jwt ?? '';
|
||||
await AuthProxyService.approveQrLogin(
|
||||
widget.pendingRef!,
|
||||
session.sessionToken.jwt,
|
||||
token,
|
||||
);
|
||||
setState(() {
|
||||
_success = true;
|
||||
|
||||
@@ -33,7 +33,6 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
// QR Login Variables
|
||||
String? _qrImageBase64;
|
||||
String? _qrPendingRef;
|
||||
String? _qrUserCode;
|
||||
bool _isQrLoading = false;
|
||||
Timer? _qrPollingTimer;
|
||||
int _qrRemainingSeconds = 0;
|
||||
@@ -207,7 +206,6 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
setState(() {
|
||||
_isQrLoading = true;
|
||||
_qrImageBase64 = null;
|
||||
_qrUserCode = null;
|
||||
_qrRemainingSeconds = 0;
|
||||
});
|
||||
|
||||
@@ -218,7 +216,6 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
_qrImageBase64 = res['qrCode'];
|
||||
_qrPendingRef = res['pendingRef'];
|
||||
_qrRemainingSeconds = res['expiresIn'] ?? 300;
|
||||
_qrUserCode = res['userCode']?.toString();
|
||||
final interval = res['interval'];
|
||||
if (interval is int && interval > 0) {
|
||||
_qrPollIntervalMs = interval * 1000;
|
||||
@@ -992,7 +989,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
codeOnly: true,
|
||||
);
|
||||
},
|
||||
child: const Text("코드만 받기(${_formatTime(_linkResendSeconds)})"),
|
||||
child: Text("코드만 받기(${_formatTime(_linkResendSeconds)})"),
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -1035,14 +1032,6 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_qrUserCode != null) ...[
|
||||
Text(
|
||||
"코드: $_qrUserCode",
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
const Text(
|
||||
"모바일 앱으로 스캔하세요",
|
||||
textAlign: TextAlign.center,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:descope/descope.dart';
|
||||
import '../../../core/services/auth_proxy_service.dart';
|
||||
import '../../../core/services/auth_token_store.dart';
|
||||
|
||||
class QRScanScreen extends StatefulWidget {
|
||||
const QRScanScreen({super.key});
|
||||
@@ -49,7 +50,8 @@ class _QRScanScreenState extends State<QRScanScreen> {
|
||||
|
||||
_log.info('QR Code detected raw: $qrData, ref: $pendingRef');
|
||||
|
||||
final sessionToken = Descope.sessionManager.session?.sessionToken.jwt;
|
||||
final sessionToken = AuthTokenStore.getToken() ??
|
||||
Descope.sessionManager.session?.sessionToken.jwt;
|
||||
if (sessionToken == null) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@@ -119,4 +121,4 @@ class _QRScanScreenState extends State<QRScanScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ server {
|
||||
|
||||
access_log /var/log/nginx/access.log json_combined;
|
||||
|
||||
# Backend API Proxy
|
||||
# --- Backend API Proxy ---
|
||||
location /api {
|
||||
proxy_pass http://baron_backend:3000;
|
||||
proxy_set_header Host $host;
|
||||
@@ -34,7 +34,55 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Frontend Static Files
|
||||
# --- Ory Stack Proxy (via Oathkeeper) ---
|
||||
# Kratos Public API
|
||||
location /auth {
|
||||
proxy_pass http://oathkeeper:4455;
|
||||
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;
|
||||
}
|
||||
|
||||
# Hydra Public API
|
||||
location /oidc {
|
||||
proxy_pass http://oathkeeper:4455;
|
||||
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;
|
||||
}
|
||||
|
||||
# --- Internal Web Apps Proxy --- 초반에는 외부 오픈 없이 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 support (for 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 support (for Vite HMR)
|
||||
# proxy_http_version 1.1;
|
||||
# proxy_set_header Upgrade $http_upgrade;
|
||||
# proxy_set_header Connection "upgrade";
|
||||
# }
|
||||
|
||||
# --- UserFront Static Files ---
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
Reference in New Issue
Block a user