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,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<!-- <!--
@@ -14,24 +14,27 @@
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
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>
</head> </head>
<body> <body>
<script src="flutter_bootstrap.js" async></script> <script src="flutter_bootstrap.js" async></script>

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": [