1
0
forked from baron/baron-sso

Baron 통합로그인 -> Baron 로그인 명칭 변경

This commit is contained in:
Lectom C Han
2026-02-05 18:36:28 +09:00
parent d3d9f7bea6
commit 62b5bdba76
13 changed files with 357 additions and 340 deletions

View File

@@ -1,6 +1,6 @@
# Baron SSO # Baron SSO
**Baron 통합로그인**은 화이트 라벨링된 가족사의 모든 소프트웨어 Auth를 총괄하는 사용자 인증/인가 허브입니다. **Baron 로그인**은 화이트 라벨링된 가족사의 모든 소프트웨어 Auth를 총괄하는 사용자 인증/인가 허브입니다.
* Ory Stack으로 모든 구성요소를 self-hosting 합니다. * Ory Stack으로 모든 구성요소를 self-hosting 합니다.
* Backend는 Go (Fiber)로 구성된 Ory Stack의 유일한 Command 전송 포인트입니다. 모든 Command는 ClickHouse로 강제 전송되며 Audit Log 시스템을 구성합니다. * Backend는 Go (Fiber)로 구성된 Ory Stack의 유일한 Command 전송 포인트입니다. 모든 Command는 ClickHouse로 강제 전송되며 Audit Log 시스템을 구성합니다.
@@ -75,7 +75,7 @@ flowchart
### 4. 주요 시나리오 (Core Scenarios) ### 4. 주요 시나리오 (Core Scenarios)
1. **Same Browser SSO**: Baron 통합로그인 서비스에 로그인된 상태에서 런처를 통해 타 앱/서비스로 이동 (자동 로그인). 1. **Same Browser SSO**: Baron 로그인 서비스에 로그인된 상태에서 런처를 통해 타 앱/서비스로 이동 (자동 로그인).
1.1. 단 약관동의(Consent) 이력이 없으면 Consent 단계로 이동 1.1. 단 약관동의(Consent) 이력이 없으면 Consent 단계로 이동
2. **Cross-Device Auth**: 이메일 SMS 등의 수단으로 링크를 전달받고 해당 링크를 사용자가 클릭하면 최초 로그인 요청한 세션이 활성화 2. **Cross-Device Auth**: 이메일 SMS 등의 수단으로 링크를 전달받고 해당 링크를 사용자가 클릭하면 최초 로그인 요청한 세션이 활성화
2.1 향후 App Push 등 2차 인증 강화수단 검토 필요 2.1 향후 App Push 등 2차 인증 강화수단 검토 필요

View File

@@ -55,9 +55,11 @@ function AppLayout() {
</div> </div>
<div> <div>
<p className="text-xs uppercase tracking-[0.18em] text-muted-foreground"> <p className="text-xs uppercase tracking-[0.18em] text-muted-foreground">
Baron Baron
</p> </p>
<h1 className="text-lg font-semibold">Admin Control</h1> <h1 className="text-lg font-semibold">
Admin Control
</h1>
</div> </div>
</div> </div>
<div className="hidden rounded-full border border-border px-3 py-2 text-xs text-muted-foreground md:inline-flex md:items-center md:gap-2"> <div className="hidden rounded-full border border-border px-3 py-2 text-xs text-muted-foreground md:inline-flex md:items-center md:gap-2">
@@ -97,8 +99,8 @@ function AppLayout() {
<div className="hidden space-y-2 px-5 pb-6 text-xs text-[var(--color-muted)] md:block"> <div className="hidden space-y-2 px-5 pb-6 text-xs text-[var(--color-muted)] md:block">
<p> /admin .</p> <p> /admin .</p>
<p> <p>
IDP API로만 , · IDP API로만 , ·
. .
</p> </p>
</div> </div>
</aside> </aside>
@@ -121,7 +123,11 @@ function AppLayout() {
className="inline-flex items-center gap-2 rounded-full border border-border px-3 py-2 text-muted-foreground transition hover:bg-muted/20" className="inline-flex items-center gap-2 rounded-full border border-border px-3 py-2 text-muted-foreground transition hover:bg-muted/20"
aria-label="테마 전환" aria-label="테마 전환"
> >
{theme === "light" ? <Sun size={16} /> : <Moon size={16} />} {theme === "light" ? (
<Sun size={16} />
) : (
<Moon size={16} />
)}
{theme === "light" ? "Light" : "Dark"} {theme === "light" ? "Light" : "Dark"}
</button> </button>
<span className="rounded-full border border-border px-3 py-2 text-muted-foreground"> <span className="rounded-full border border-border px-3 py-2 text-muted-foreground">

View File

@@ -237,7 +237,7 @@ func (h *AuthHandler) SendSignupEmailCode(c *fiber.Ctx) error {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Email service not configured"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Email service not configured"})
} }
subject := "[Baron 통합로그인] 회원가입 인증코드" subject := "[Baron 로그인] 회원가입 인증코드"
body := fmt.Sprintf(` body := fmt.Sprintf(`
<div style="padding: 20px; font-family: sans-serif;"> <div style="padding: 20px; font-family: sans-serif;">
<h2>이메일 인증</h2> <h2>이메일 인증</h2>
@@ -287,7 +287,7 @@ func (h *AuthHandler) SendSignupSmsCode(c *fiber.Ctx) error {
h.saveSignupState(key, newState, signupStateExpiration) h.saveSignupState(key, newState, signupStateExpiration)
// 4. Send SMS // 4. Send SMS
content := fmt.Sprintf("[Baron 통합로그인] 인증번호 [%s]를 입력해주세요.", code) content := fmt.Sprintf("[Baron 로그인] 인증번호 [%s]를 입력해주세요.", code)
go h.SmsService.SendSms(phone, content) go h.SmsService.SendSms(phone, content)
return c.JSON(fiber.Map{"message": "Verification code sent"}) return c.JSON(fiber.Map{"message": "Verification code sent"})
@@ -808,7 +808,7 @@ func (h *AuthHandler) SendSms(c *fiber.Ctx) error {
sanitizedPhone := strings.ReplaceAll(req.PhoneNumber, "-", "") sanitizedPhone := strings.ReplaceAll(req.PhoneNumber, "-", "")
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
code := fmt.Sprintf("%06d", rand.Intn(1000000)) code := fmt.Sprintf("%06d", rand.Intn(1000000))
content := fmt.Sprintf("[Baron 통합로그인] 인증번호: %s", code) content := fmt.Sprintf("[Baron 로그인] 인증번호: %s", code)
h.RedisService.StoreVerificationCode(sanitizedPhone, code) h.RedisService.StoreVerificationCode(sanitizedPhone, code)
if err := h.SmsService.SendSms(sanitizedPhone, content); err != nil { if err := h.SmsService.SendSms(sanitizedPhone, content); err != nil {
@@ -991,10 +991,10 @@ func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Email service not configured"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Email service not configured"})
} }
subject := "[Baron 통합로그인] 링크" subject := "[Baron 로그인] 링크"
body := fmt.Sprintf(` body := fmt.Sprintf(`
<div style="font-family: sans-serif; padding: 20px; border: 1px solid #eee; border-radius: 10px; max-width: 500px;"> <div style="font-family: sans-serif; padding: 20px; border: 1px solid #eee; border-radius: 10px; max-width: 500px;">
<h2 style="color: #1A1F2C;">Baron SSO 로그인</h2> <h2 style="color: #1A1F2C;">Baron 로그인</h2>
<p>안녕하세요,</p> <p>안녕하세요,</p>
<p>아래 버튼을 클릭하여 로그인을 완료해 주세요. 이 링크는 5분 동안 유효합니다.</p> <p>아래 버튼을 클릭하여 로그인을 완료해 주세요. 이 링크는 5분 동안 유효합니다.</p>
<div style="margin: 30px 0;"> <div style="margin: 30px 0;">
@@ -1016,7 +1016,7 @@ func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error {
} }
} else { } else {
// Send SMS // Send SMS
content := fmt.Sprintf("[Baron 통합로그인] 로그인 링크: %s | 코드: %s", link, userCode) content := fmt.Sprintf("[Baron 로그인] 로그인 링크: %s | 코드: %s", link, userCode)
if drySend { if drySend {
slog.Info("[Enchanted][DrySend] SMS send skipped", "loginID", loginID, "content", content) slog.Info("[Enchanted][DrySend] SMS send skipped", "loginID", loginID, "content", content)
} else { } else {
@@ -1677,10 +1677,10 @@ func (h *AuthHandler) InitiatePasswordReset(c *fiber.Ctx) error {
ale.Log(slog.LevelError, "Email service not configured") ale.Log(slog.LevelError, "Email service not configured")
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Email service not configured"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Email service not configured"})
} }
subject := "[Baron 통합로그인] 비밀번호 재설정" subject := "[Baron 로그인] 비밀번호 재설정"
body := fmt.Sprintf(` body := fmt.Sprintf(`
<div style="font-family: sans-serif; padding: 20px; border: 1px solid #eee; border-radius: 10px; max-width: 500px;"> <div style="font-family: sans-serif; padding: 20px; border: 1px solid #eee; border-radius: 10px; max-width: 500px;">
<h2 style="color: #1A1F2C;">Baron SSO 비밀번호 재설정</h2> <h2 style="color: #1A1F2C;">Baron 로그인 비밀번호 재설정</h2>
<p>아래 버튼을 클릭해 비밀번호 재설정을 진행해 주세요. 링크는 15분간 유효합니다.</p> <p>아래 버튼을 클릭해 비밀번호 재설정을 진행해 주세요. 링크는 15분간 유효합니다.</p>
<div style="margin: 30px 0;"> <div style="margin: 30px 0;">
<a href="%s" style="background-color: #1A1F2C; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold;">비밀번호 재설정</a> <a href="%s" style="background-color: #1A1F2C; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold;">비밀번호 재설정</a>
@@ -1700,7 +1700,7 @@ func (h *AuthHandler) InitiatePasswordReset(c *fiber.Ctx) error {
} }
} }
} else { } else {
resetSms := fmt.Sprintf("[Baron 통합로그인] 비밀번호 재설정 링크: %s", resetLink) resetSms := fmt.Sprintf("[Baron 로그인] 비밀번호 재설정 링크: %s", resetLink)
if drySend { if drySend {
ale.Log(slog.LevelInfo, "SMS send skipped (dry-send)", slog.String("loginId", loginID), slog.String("content", resetSms)) ale.Log(slog.LevelInfo, "SMS send skipped (dry-send)", slog.String("loginId", loginID), slog.String("content", resetSms))
} else { } else {
@@ -1736,7 +1736,7 @@ func (h *AuthHandler) VerifyPasswordResetPage(c *fiber.Ctx) error {
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Baron SSO - 비밀번호 재설정</title> <title>Baron 로그인 - 비밀번호 재설정</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<style> <style>
body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f5f5f5; } body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f5f5f5; }
@@ -2227,7 +2227,7 @@ func (h *AuthHandler) buildKratosCourierMessage(req *kratosCourierRequest) (stri
body := strings.TrimSpace(req.Body) body := strings.TrimSpace(req.Body)
if body != "" || subject != "" { if body != "" || subject != "" {
if subject == "" { if subject == "" {
subject = "[Baron 통합로그인] 알림" subject = "[Baron 로그인] 알림"
} }
return subject, body return subject, body
} }
@@ -2251,9 +2251,9 @@ func (h *AuthHandler) buildKratosCourierMessage(req *kratosCourierRequest) (stri
if subject == "" { if subject == "" {
if label == "알림" { if label == "알림" {
subject = "[Baron 통합로그인] 알림" subject = "[Baron 로그인] 알림"
} else { } else {
subject = fmt.Sprintf("[Baron 통합로그인] %s 코드", label) subject = fmt.Sprintf("[Baron 로그인] %s 코드", label)
} }
} }
@@ -2272,10 +2272,10 @@ func (h *AuthHandler) buildKratosCourierMessage(req *kratosCourierRequest) (stri
} }
if code == "" { if code == "" {
return subject, fmt.Sprintf("[Baron 통합로그인] %s 요청이 도착했습니다", label) return subject, fmt.Sprintf("[Baron 로그인] %s 요청이 도착했습니다", label)
} }
message := fmt.Sprintf("[Baron 통합로그인] %s 코드: %s", label, code) message := fmt.Sprintf("[Baron 로그인] %s 코드: %s", label, code)
if label == "로그인" { if label == "로그인" {
baseURL := os.Getenv("USERFRONT_URL") baseURL := os.Getenv("USERFRONT_URL")
if baseURL == "" { if baseURL == "" {
@@ -2320,9 +2320,9 @@ func (h *AuthHandler) buildKratosShortSmsBody(req *kratosCourierRequest, loginID
return "" return ""
} }
if h.isSmsCodeOnly(loginID) { if h.isSmsCodeOnly(loginID) {
return fmt.Sprintf("[Baron 통합로그인] 로그인 코드: %s", shortCode) return fmt.Sprintf("[Baron 로그인] 로그인 코드: %s", shortCode)
} }
return fmt.Sprintf("[Baron 통합로그인] %s", link) return fmt.Sprintf("[Baron 로그인] %s", link)
} }
func (h *AuthHandler) buildKratosShortEmailBody(req *kratosCourierRequest, loginID string) (string, string) { func (h *AuthHandler) buildKratosShortEmailBody(req *kratosCourierRequest, loginID string) (string, string) {
@@ -2330,10 +2330,10 @@ func (h *AuthHandler) buildKratosShortEmailBody(req *kratosCourierRequest, login
if !ok { if !ok {
return "", "" return "", ""
} }
subject := "[Baron 통합로그인] 로그인 링크" subject := "[Baron 로그인] 로그인 링크"
body := fmt.Sprintf(` body := fmt.Sprintf(`
<div style="font-family: sans-serif; padding: 20px; border: 1px solid #eee; border-radius: 10px; max-width: 500px;"> <div style="font-family: sans-serif; padding: 20px; border: 1px solid #eee; border-radius: 10px; max-width: 500px;">
<h2 style="color: #1A1F2C;">Baron SSO 로그인</h2> <h2 style="color: #1A1F2C;">Baron 로그인</h2>
<p>아래 버튼을 클릭하여 로그인을 완료해 주세요.</p> <p>아래 버튼을 클릭하여 로그인을 완료해 주세요.</p>
<div style="margin: 24px 0;"> <div style="margin: 24px 0;">
<a href="%s" style="background-color: #1A1F2C; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold;">로그인 완료하기</a> <a href="%s" style="background-color: #1A1F2C; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold;">로그인 완료하기</a>
@@ -3074,7 +3074,7 @@ func (h *AuthHandler) GetAuthTimeline(c *fiber.Ctx) error {
items := make([]authTimelineItem, 0, len(authLogs)+len(oathkeeperLogs)) items := make([]authTimelineItem, 0, len(authLogs)+len(oathkeeperLogs))
for i := range authLogs { for i := range authLogs {
log := authLogs[i] log := authLogs[i]
appName := "Baron 통합로그인" appName := "Baron 로그인"
clientID := "" clientID := ""
path := strings.ToLower(extractAuditPath(log)) path := strings.ToLower(extractAuditPath(log))
if strings.Contains(path, "/api/v1/auth/oidc/login/accept") { if strings.Contains(path, "/api/v1/auth/oidc/login/accept") {
@@ -3223,7 +3223,6 @@ func (h *AuthHandler) ListLinkedRps(c *fiber.Ctx) error {
var sessions []domain.HydraConsentSession var sessions []domain.HydraConsentSession
var lastErr error var lastErr error
hasSuccess := false hasSuccess := false
for _, subject := range subjects { for _, subject := range subjects {
@@ -3572,7 +3571,6 @@ func (h *AuthHandler) RejectConsentRequest(c *fiber.Ctx) error {
return c.JSON(rejectResp) return c.JSON(rejectResp)
} }
func (h *AuthHandler) AcceptOidcLoginRequest(c *fiber.Ctx) error { func (h *AuthHandler) AcceptOidcLoginRequest(c *fiber.Ctx) error {
var req struct { var req struct {
LoginChallenge string `json:"login_challenge"` LoginChallenge string `json:"login_challenge"`
@@ -4973,7 +4971,7 @@ func (h *AuthHandler) SendUpdateCode(c *fiber.Ctx) error {
h.RedisService.Set(key, code, 5*time.Minute) h.RedisService.Set(key, code, 5*time.Minute)
// Send SMS // Send SMS
content := fmt.Sprintf("[Baron 통합로그인] 정보 수정 인증번호: [%s]", code) content := fmt.Sprintf("[Baron 로그인] 정보 수정 인증번호: [%s]", code)
go h.SmsService.SendSms(phone, content) go h.SmsService.SendSms(phone, content)
return c.JSON(fiber.Map{"message": "인증번호가 전송되었습니다."}) return c.JSON(fiber.Map{"message": "인증번호가 전송되었습니다."})

View File

@@ -36,9 +36,11 @@ function AppLayout() {
</div> </div>
<div> <div>
<p className="text-xs uppercase tracking-[0.18em] text-muted-foreground"> <p className="text-xs uppercase tracking-[0.18em] text-muted-foreground">
Baron Baron
</p> </p>
<h1 className="text-lg font-semibold">Developer Console</h1> <h1 className="text-lg font-semibold">
Developer Console
</h1>
</div> </div>
</div> </div>
<div className="hidden rounded-full border border-border px-3 py-2 text-xs text-muted-foreground md:inline-flex md:items-center md:gap-2"> <div className="hidden rounded-full border border-border px-3 py-2 text-xs text-muted-foreground md:inline-flex md:items-center md:gap-2">
@@ -74,7 +76,10 @@ function AppLayout() {
</nav> </nav>
<div className="hidden space-y-2 px-5 pb-6 text-xs text-[var(--color-muted)] md:block"> <div className="hidden space-y-2 px-5 pb-6 text-xs text-[var(--color-muted)] md:block">
<p> .</p> <p> .</p>
<p> .</p> <p>
.
</p>
</div> </div>
</aside> </aside>
@@ -96,7 +101,11 @@ function AppLayout() {
className="inline-flex items-center gap-2 rounded-full border border-border px-3 py-2 text-muted-foreground transition hover:bg-muted/20" className="inline-flex items-center gap-2 rounded-full border border-border px-3 py-2 text-muted-foreground transition hover:bg-muted/20"
aria-label="테마 전환" aria-label="테마 전환"
> >
{theme === "light" ? <Sun size={16} /> : <Moon size={16} />} {theme === "light" ? (
<Sun size={16} />
) : (
<Moon size={16} />
)}
{theme === "light" ? "Light" : "Dark"} {theme === "light" ? "Light" : "Dark"}
</button> </button>
</div> </div>

View File

@@ -1,4 +1,4 @@
[Baron 통합로그인] 로그인 링크 [Baron 로그인] 로그인 링크
# 운영 링크: https://app.brsw.kr/verify?loginId={{ .To }}&code={{ .LoginCode }} # 운영 링크: https://app.brsw.kr/verify?loginId={{ .To }}&code={{ .LoginCode }}
http://localhost:5000/verify?loginId={{ .To }}&code={{ .LoginCode }} http://localhost:5000/verify?loginId={{ .To }}&code={{ .LoginCode }}
코드: {{ .LoginCode }} 코드: {{ .LoginCode }}

View File

@@ -118,7 +118,7 @@ flowchart
- RP별 Consent 관리 - RP별 Consent 관리
### 5. 주요 시나리오 (Core Scenarios) ### 5. 주요 시나리오 (Core Scenarios)
1. **Same Browser SSO**: Baron 통합로그인 서비스에 로그인된 상태에서 런처를 통해 타 앱/서비스로 이동 (자동 로그인). 1. **Same Browser SSO**: Baron 로그인 서비스에 로그인된 상태에서 런처를 통해 타 앱/서비스로 이동 (자동 로그인).
1.1. 단 약관동의(Consent) 이력이 없으면 Consent 단계로 이동 1.1. 단 약관동의(Consent) 이력이 없으면 Consent 단계로 이동
2. **Cross-Device Auth**: 이메일 SMS 등의 수단으로 링크를 전달받고 해당 링크를 사용자가 클릭하면 최초 로그인 요청한 세션이 활성화 2. **Cross-Device Auth**: 이메일 SMS 등의 수단으로 링크를 전달받고 해당 링크를 사용자가 클릭하면 최초 로그인 요청한 세션이 활성화
2.1 향후 App Push 등 2차 인증 강화수단 검토 필요 2.1 향후 App Push 등 2차 인증 강화수단 검토 필요

View File

@@ -9,17 +9,19 @@ import json
import sys import sys
from dotenv import load_dotenv from dotenv import load_dotenv
def get_env_variable(key, env_file): def get_env_variable(key, env_file):
"""Reads an environment variable from a given .env file.""" """Reads an environment variable from a given .env file."""
with open(env_file, 'r') as f: with open(env_file, "r") as f:
for line in f: for line in f:
line = line.strip() line = line.strip()
if line and not line.startswith('#'): if line and not line.startswith("#"):
k, v = line.split('=', 1) k, v = line.split("=", 1)
if k == key: if k == key:
return v.strip() return v.strip()
return None return None
def main(): def main():
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("Usage: python test/test_sms.py <recipient_phone_number>") print("Usage: python test/test_sms.py <recipient_phone_number>")
@@ -28,10 +30,10 @@ def main():
recipient_phone = sys.argv[1] recipient_phone = sys.argv[1]
# Load environment variables from .env or .env.sample # Load environment variables from .env or .env.sample
env_path = os.path.join(os.getcwd(), '.env') env_path = os.path.join(os.getcwd(), ".env")
if not os.path.exists(env_path): if not os.path.exists(env_path):
print("Info: .env file not found. Using .env.sample as a fallback.") print("Info: .env file not found. Using .env.sample as a fallback.")
env_path = os.path.join(os.getcwd(), '.env.sample') env_path = os.path.join(os.getcwd(), ".env.sample")
if not os.path.exists(env_path): if not os.path.exists(env_path):
print("Error: No configuration file found (.env or .env.sample).") print("Error: No configuration file found (.env or .env.sample).")
@@ -43,7 +45,9 @@ def main():
sender_phone = get_env_variable("NAVER_SENDER_PHONE_NUMBER", env_path) sender_phone = get_env_variable("NAVER_SENDER_PHONE_NUMBER", env_path)
if not all([access_key, secret_key, service_id, sender_phone]): if not all([access_key, secret_key, service_id, sender_phone]):
print(f"Error: One or more required environment variables are missing in {env_path}.") print(
f"Error: One or more required environment variables are missing in {env_path}."
)
sys.exit(1) sys.exit(1)
timestamp = str(int(time.time() * 1000)) timestamp = str(int(time.time() * 1000))
@@ -52,8 +56,8 @@ def main():
# Create the signature for the API request # Create the signature for the API request
message = f"POST {api_path}\n{timestamp}\n{access_key}" message = f"POST {api_path}\n{timestamp}\n{access_key}"
h = hmac.new(bytes(secret_key, 'UTF-8'), bytes(message, 'UTF-8'), hashlib.sha256) h = hmac.new(bytes(secret_key, "UTF-8"), bytes(message, "UTF-8"), hashlib.sha256)
signature = base64.b64encode(h.digest()).decode('UTF-8') signature = base64.b64encode(h.digest()).decode("UTF-8")
# Construct the JSON request body # Construct the JSON request body
json_body = { json_body = {
@@ -61,19 +65,15 @@ def main():
"contentType": "COMM", "contentType": "COMM",
"countryCode": "82", "countryCode": "82",
"from": sender_phone, "from": sender_phone,
"content": "[Baron 통합로그인] Test message from Python script.", "content": "[Baron 로그인] Test message from Python script.",
"messages": [ "messages": [{"to": recipient_phone}],
{
"to": recipient_phone
}
]
} }
headers = { headers = {
"Content-Type": "application/json; charset=utf-8", "Content-Type": "application/json; charset=utf-8",
"x-ncp-apigw-timestamp": timestamp, "x-ncp-apigw-timestamp": timestamp,
"x-ncp-iam-access-key": access_key, "x-ncp-iam-access-key": access_key,
"x-ncp-apigw-signature-v2": signature "x-ncp-apigw-signature-v2": signature,
} }
print("========================================") print("========================================")
@@ -92,7 +92,7 @@ def main():
print(json.dumps(response.json(), indent=2, ensure_ascii=False)) print(json.dumps(response.json(), indent=2, ensure_ascii=False))
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
print(f"Request failed: {e}") print(f"Request failed: {e}")
if hasattr(e, 'response') and e.response is not None: if hasattr(e, "response") and e.response is not None:
print("API Error Response:") print("API Error Response:")
try: try:
print(json.dumps(e.response.json(), indent=2, ensure_ascii=False)) print(json.dumps(e.response.json(), indent=2, ensure_ascii=False))
@@ -106,5 +106,6 @@ def main():
print(" Request complete.") print(" Request complete.")
print("========================================") print("========================================")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -1028,7 +1028,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text( Text(
"Baron 통합로그인", "Baron 로그인",
style: TextStyle( style: TextStyle(
fontSize: 32, fontSize: 32,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@@ -801,10 +801,10 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
String _appLabelForPath(String path) { String _appLabelForPath(String path) {
if (path.startsWith('/api/v1/auth')) { if (path.startsWith('/api/v1/auth')) {
return 'Baron 통합로그인'; return 'Baron 로그인';
} }
if (path.startsWith('/api/v1/user')) { if (path.startsWith('/api/v1/user')) {
return 'Baron 통합로그인'; return 'Baron 로그인';
} }
if (path.startsWith('/api/v1/dev')) { if (path.startsWith('/api/v1/dev')) {
return 'Dev Console'; return 'Dev Console';
@@ -812,7 +812,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
if (path.startsWith('/api/v1/admin')) { if (path.startsWith('/api/v1/admin')) {
return 'Admin Console'; return 'Admin Console';
} }
return 'Baron 통합로그인'; return 'Baron 로그인';
} }
@override @override
@@ -831,7 +831,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
backgroundColor: _subtle, backgroundColor: _subtle,
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
'Baron 통합로그인', 'Baron 로그인',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
elevation: 0, elevation: 0,
@@ -890,7 +890,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
const SizedBox(height: 12), const SizedBox(height: 12),
_buildPastRps(isMobile), _buildPastRps(isMobile),
const SizedBox(height: 28), const SizedBox(height: 28),
_buildSectionTitle('접속이력', 'Baron 통합로그인 기준의 최근 접근 기록입니다.'), _buildSectionTitle('접속이력', 'Baron 로그인 기준의 최근 접근 기록입니다.'),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildAccessHistory(timelineWide), _buildAccessHistory(timelineWide),
], ],

View File

@@ -971,7 +971,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
backgroundColor: _subtle, backgroundColor: _subtle,
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
'Baron 통합로그인', 'Baron 로그인',
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
elevation: 0, elevation: 0,

View File

@@ -295,7 +295,7 @@ class BaronSSOApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp.router( return MaterialApp.router(
title: 'Baron 통합로그인', title: 'Baron 로그인',
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF1A1F2C), // Dark Navy/Black base seedColor: const Color(0xFF1A1F2C), // Dark Navy/Black base

View File

@@ -1,6 +1,6 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<!-- <!--
If you are serving your web app in a path other than the root, change the If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from. href value below to reflect the base path you are serving from.
@@ -14,26 +14,29 @@
This is a placeholder for base href that will be replaced by the value of This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`. the `--base-href` argument provided to `flutter build`.
--> -->
<base href="/"> <base href="/" />
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta name="description" content="A new Flutter project."> <meta name="description" content="A new Flutter project." />
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="Baron 통합로그인"> <meta name="apple-mobile-web-app-title" content="Baron 로그인" />
<link rel="apple-touch-icon" href="icons/Icon-192.png"> <link rel="apple-touch-icon" href="icons/Icon-192.png" />
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png" />
<title>Baron 통합로그인</title> <title>Baron 로그인</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link
</head> href="https://fonts.googleapis.com/icon?family=Material+Icons"
<body> rel="stylesheet"
/>
</head>
<body>
<script src="flutter_bootstrap.js" async></script> <script src="flutter_bootstrap.js" async></script>
</body> </body>
</html> </html>

View File

@@ -1,11 +1,11 @@
{ {
"name": "Baron 통합로그인", "name": "Baron 로그인",
"short_name": "Baron 통합로그인", "short_name": "Baron 로그인",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"background_color": "#0175C2", "background_color": "#0175C2",
"theme_color": "#0175C2", "theme_color": "#0175C2",
"description": "Baron 통합로그인 사용자 포털.", "description": "Baron 로그인 사용자 포털.",
"orientation": "portrait-primary", "orientation": "portrait-primary",
"prefer_related_applications": false, "prefer_related_applications": false,
"icons": [ "icons": [