첫 커밋: 로컬 프로젝트 업로드

This commit is contained in:
2026-06-10 15:51:34 +09:00
commit 6a8dbeb2e9
1211 changed files with 312864 additions and 0 deletions

45
baron-sso/userfront/.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@@ -0,0 +1,36 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "f6ff1529fd6d8af5f706051d9251ac9231c83407"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
- platform: android
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
- platform: ios
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
- platform: web
create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,38 @@
FROM ghcr.io/cirruslabs/flutter:3.38.0 AS dev
ENV RUN_FLUTTER_AS_ROOT=true
WORKDIR /workspace
COPY scripts ./scripts
COPY locales ./locales
COPY userfront ./userfront
WORKDIR /workspace/userfront
RUN flutter pub get
EXPOSE 5000
CMD ["sh", "./scripts/dev-server.sh"]
# Stage 1: Build Flutter
FROM ghcr.io/cirruslabs/flutter:3.38.0 AS build
ENV RUN_FLUTTER_AS_ROOT=true
WORKDIR /app
COPY . .
# Get dependencies and build for web
RUN /bin/sh ./scripts/sync_userfront_locales.sh
WORKDIR /app/userfront
RUN flutter pub get
RUN rm -rf build/web && flutter build web --release --wasm
FROM node:24-alpine AS optimize
WORKDIR /work
COPY --from=build /app/userfront/build/web /work/build/web
COPY userfront/scripts/optimize-web-build.mjs /work/scripts/optimize-web-build.mjs
RUN node /work/scripts/optimize-web-build.mjs /work/build/web
# Stage 2: Serve with Nginx
FROM alpine:3.23 AS production
RUN apk add --no-cache nginx nginx-mod-http-brotli
# Copy built assets
COPY --from=optimize /work/build/web /usr/share/nginx/html
# Copy custom Nginx config
COPY userfront/nginx.conf /etc/nginx/http.d/default.conf
EXPOSE 5000
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
baron-sso/userfront/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "kr.co.baroncs.userfront"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "kr.co.baroncs.userfront"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,46 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA"/>
<application
android:label="userfront"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,5 @@
package kr.co.baroncs.userfront
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip

View File

@@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}
include(":app")

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

View File

@@ -0,0 +1,709 @@
[domain]
[domain.affiliation]
affiliate = "Affiliate"
general = "General"
[domain.company]
baron = "Baron"
halla = "Halla"
hanmac = "Hanmac"
jangheon = "Jangheon"
ptc = "PTC"
saman = "Saman"
[domain.tenant_type]
company = "Company"
company_group = "Company Group"
organization = "Organization"
personal = "Personal"
user_group = "User Group"
[err.userfront]
[err.userfront.auth_proxy]
consent_accept = "Failed to accept the consent request."
consent_fetch = "Failed to load consent details."
consent_reject = "Failed to reject the consent request."
linked_app_revoke = "Failed to revoke the linked application."
login_failed = "Login failed."
oidc_accept = "OIDC Accept"
password_reset_complete = "Failed to complete the password reset."
password_reset_init = "Failed to start the password reset."
[err.userfront.profile]
load_failed = "Failed to load the profile."
password_change_failed = "Password Change Failed"
send_code_failed = "Failed to send the verification code."
update_failed = "Failed to update the profile."
verify_code_failed = "Verification failed."
[err.userfront.session]
missing = "No active session was found."
[msg.userfront]
greeting = "Hello, {name}."
[msg.userfront.audit]
browser = "Browser: {value}"
date = "Date: {value}"
device = "Device: {value}"
end = "No more items to show."
filtered_empty = "No sign-in history matches the active session filter."
ip = "IP address: {value}"
load_more_error = "Could not load more history."
result = "Result: {value}"
session_id = "Session ID: {value}"
status = "Status: pending"
[msg.userfront.consent]
accept_error = "Failed to process consent: {error}"
client_id = "Client ID: {id}"
client_unknown = "Unknown application"
description = "The service below is requesting access to your account information.\\\\nPlease choose whether to continue."
load_error = "Failed to load consent information: {error}"
missing_redirect = "Consent was processed, but the redirect URL was missing."
redirect_notice = "After consent, you will be redirected automatically."
scope_count = "Total {count}"
[msg.userfront.consent.cancel]
confirm = "If you cancel consent, you will not be able to use this service. Do you want to cancel?"
error = "An error occurred while cancelling consent: {error}"
[msg.userfront.consent.scope]
email = "Email address (account identification and notifications)"
offline_access = "Offline access (keep signed in)"
openid = "OpenID authentication information (signin session check)"
phone = "Phone number (identity verification and notifications)"
profile = "Basic profile information (name, user identifier)"
[msg.userfront.dashboard]
approved_device = "Approved device: {device}"
approved_ip = "Approved IP: {ip}"
audit_empty = "No recent sign-in activity."
audit_load_error = "Could not load sign-in history."
auto_login_supported = "You can sign in without an extra login when opening this linked app."
auth_method = "Auth method: {method}"
client_id = "Client ID: {id}"
client_id_missing = "No client ID available."
current_status = "Current status: {status}"
last_auth = "Last signed in: {value}"
link_status = "Link status: {status}"
link_missing = "This app does not have a launch URL configured."
link_open_error = "Could not open the app link."
render_error = "Dashboard render error: {error}"
session_id_copied = "Session ID copied."
[msg.userfront.dashboard.activities]
empty = "No linked apps yet."
empty_detail = "Linked apps and their latest activity will appear here."
error = "Could not load linked apps."
[msg.userfront.dashboard.sessions]
browser = "Browser: {value}"
empty = "No active sessions."
empty_detail = "Devices signed in with this account will appear here."
error = "Could not load sessions."
os = "OS: {value}"
recent_app = "Recent app: {app}"
session_id = "Session ID: {id}"
[msg.userfront.dashboard.sessions.revoke]
confirm = "End the session for {target}?\nThat device will need to sign in again."
error = "Could not end the session: {error}"
success = "The session has been ended."
[msg.userfront.dashboard.approved_session]
copy_click = "{label}: {id}\\\\\\\\\\\\\\\\nClick to copy."
copy_tap = "{label}: {id}\\\\\\\\\\\\\\\\nTap to copy."
none = "No {label}"
[msg.userfront.dashboard.revoke]
confirm = "Disconnect {app}?\\\\\\\\\\\\\\\\nYou will need to grant access again the next time you sign in."
error = "Could not disconnect the app: {error}"
success = "{app} has been disconnected."
[msg.userfront.dashboard.scopes]
empty = "No scopes were requested."
[msg.userfront.dashboard.timeline]
load_error = "Could not load sign-in history."
[msg.userfront.error]
detail_contact = "If the problem continues, please contact your administrator."
detail_generic = "Something went wrong."
detail_request = "We had trouble processing your request."
id = "Error ID: {id}"
title = "An error occurred during authentication."
title_generic = "An error occurred."
title_with_code = "Error: {code}"
type = "Error type: {type}"
[msg.userfront.error.tenant]
account = "Account"
account_unknown = "Unknown"
affiliated_tenants = "All affiliated tenants"
allowed_box_title = "Allowed tenants"
allowed_tenants = "Allowed tenants"
detail = "The currently signed-in account cannot access this application."
load_failed = "We could not confirm the account details. Please try again."
loading = "Loading the current account details."
lookup_fallback = "Some fields could not be verified because the access context was incomplete."
page_title = "Access to this application is restricted"
primary_tenant = "Primary affiliated tenant"
tenant = "Tenant"
tenant_unknown = "Unknown"
title = "Access restriction details"
[msg.userfront.error.ory]
"$normalizedCode" = "{error}"
access_denied = "The user denied the consent request."
consent_required = "Consent is required to continue."
interaction_required = "Additional interaction is required. Please try again."
invalid_client = "Client authentication failed."
invalid_grant = "The authorization grant is invalid or expired."
invalid_request = "The request is invalid."
invalid_scope = "The requested scope is invalid."
login_required = "Login is required."
request_forbidden = "The request was forbidden."
server_error = "An authentication server error occurred."
temporarily_unavailable = "The authentication server is temporarily unavailable."
unauthorized_client = "The client is not authorized for this request."
unsupported_response_type = "The response type is not supported."
[msg.userfront.error.whitelist]
"$normalizedCode" = "{error}"
bad_request = "Please check your input."
invalid_session = "Your session has expired. Please sign in again."
not_found = "The requested page could not be found."
password_or_email_mismatch = "Email or password does not match."
rate_limited = "Too many requests. Please try again later."
recovery_expired = "The recovery link has expired. Please request a new one."
recovery_invalid = "The recovery link is invalid."
settings_disabled = "Account settings are currently unavailable."
verification_required = "Additional verification is required. Please follow the instructions."
[msg.userfront.forgot]
description = "Enter the email address or phone number linked to your account and we will send you a password reset link."
dry_send = "Dry-send mode: no email or SMS was actually sent."
error = "Failed to send the reset link: {error}"
input_required = "Enter your email address or phone number."
sent = "A password reset link has been sent. Check your email or SMS."
[msg.userfront.login]
cookie_check_failed = "Could not verify your sign-in state: {error}"
dry_send = "Dry-send mode: no email or SMS was actually sent."
link_failed = "Could not complete link sign-in: {error}"
link_send_failed = "Failed to send the sign-in link: {error}"
link_sent_email = "We sent a sign-in link to your email address."
link_sent_phone = "We sent a sign-in link to your phone number."
link_timeout = "Time expired."
no_account = "New to Baron?"
oidc_failed = "OIDC sign-in failed. Please try again."
qr_expired = "Time expired."
qr_init_failed = "Failed to initialize QR sign-in: {error}"
qr_login_required = "You need to be signed in to approve a QR sign-in."
token_missing = "Could not find the sign-in token."
verification_failed = "Failed to approve the sign-in request: {error}"
[msg.userfront.login.link]
approved = "Sign-in approved. You will be redirected to the sign-in page shortly."
helper = "We will send a sign-in link using the information you enter."
missing_login_id = "Enter your email address or phone number."
missing_phone = "Enter your phone number."
resend_wait = "You can resend in {time}."
short_code_help = "You can also sign in with the last 2 letters and 6 digits from the link you received."
[msg.userfront.login.password]
failed = "Sign-in failed: {error}"
missing_credentials = "Enter both your email or phone number and your password."
[msg.userfront.login.qr]
load_failed = "Could not load the QR code."
scan_hint = "Scan it with the mobile app."
[msg.userfront.login.short_code]
invalid = "Enter the 2 letters and 6 digits from your code."
[msg.userfront.login.unregistered]
body = "We could not find an account for that information.\\\\\\\\\\\\\\\\nPlease sign up before continuing."
[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 = "Your requested sign-in is complete."
pending_remote = "Checking the sign-in approval request. Please wait."
close_hint = "You can close this window now."
success = "Sign-in approval completed."
[msg.userfront.login_success]
subtitle = "You have signed in successfully."
[msg.userfront.profile]
department_missing = "No department information"
department_required = "Enter your department."
email_missing = "No email address"
greeting = "Hello, {name}."
load_failed = "Could not load your profile."
name_missing = "No name provided"
name_required = "Enter your name."
phone_required = "Enter your phone number."
phone_verify_required = "Phone verification is required."
update_failed = "Failed to update your profile: {error}"
update_success = "Your profile has been updated."
[msg.userfront.profile.password]
change_failed = "Failed to change your password: {error}"
changed = "Your password has been changed."
current_required = "Enter your current password."
mismatch = "The new passwords do not match."
new_required = "Enter a new password."
subtitle = "Verify your current password before setting a new one."
[msg.userfront.profile.phone]
code_sent = "A verification code has been sent."
send_failed = "Failed to send the code: {error}"
verified = "Phone number verified."
verify_failed = "Verification failed: {error}"
verify_notice = "SMS verification is required to change your phone number."
[msg.userfront.profile.section]
basic = "Manage your basic account information."
organization = "Your organization and affiliation details."
security = "Keep your password secure."
[msg.userfront.qr]
approve_error = "QR approval failed: {error}"
approve_success = "QR approval complete. Continue on your desktop."
camera_error = "Camera error: {error}"
permission_error = "Could not request camera access. Check your browser or OS settings."
permission_required = "Camera access is required."
[msg.userfront.reset]
invalid_body = "This password reset link is invalid or has expired. Please request a new one."
invalid_link = "This reset link is invalid. Missing loginId or token."
invalid_title = "Invalid reset link"
policy_loading = "Loading the password policy..."
success = "Your password has been changed successfully. Please sign in again."
[msg.userfront.reset.error]
empty_password = "Please enter Password."
generic = "Failed to change your password: {error}"
lowercase = "Include at least one lowercase letter."
min_length = "Use at least {count} characters."
min_types = "Use at least {count} character types: uppercase, lowercase, number, or symbol."
mismatch = "The passwords do not match."
number = "Include at least one number."
symbol = "Include at least one symbol."
uppercase = "Include at least one uppercase letter."
[msg.userfront.reset.policy]
lowercase = "At least one lowercase letter"
min_length = "At least {count} characters"
min_types = "At least {count} character types"
number = "At least one number"
symbol = "At least one symbol"
uppercase = "At least one uppercase letter"
[msg.userfront.sections]
apps_subtitle = "Your linked apps and their latest sign-in status."
audit_subtitle = "Recent access history for Baron sign-in."
sessions_subtitle = "Your currently signed-in devices and browser sessions."
[msg.userfront.settings]
disabled = "Account settings are currently unavailable."
[msg.userfront.signup]
failed = "Failed"
privacy_full = "Privacy collection and use consent notice."
tos_full = "Service terms of use notice."
[msg.userfront.signup.agreement]
all_hint = "Agree to both required documents to continue to the next step."
description = "Review the service terms and privacy collection notice, then agree to continue."
privacy_summary = "Review what personal data is collected, why it is used, and how it is retained."
progress = "{count} of {total} required agreements completed"
title = "Please review and agree to the terms to continue."
tos_summary = "Review the service terms, usage conditions, and responsibilities."
[msg.userfront.signup.auth]
affiliate_notice = "If you are an affiliate employee, use your official company email address."
title = "Verify your email and phone number."
[msg.userfront.signup.email]
code_mismatch = "The verification code does not match."
duplicate = "This email address is already registered."
invalid = "Enter a valid email address."
send_failed = "Failed to send the email: {error}"
verified = "Email verified."
verify_failed = "Email verification failed: {error}"
[msg.userfront.signup.password]
length_required = "Your password must be at least 12 characters long."
lowercase_required = "Include at least one lowercase letter."
mismatch = "The passwords do not match."
number_required = "Include at least one number."
symbol_required = "Include at least one symbol."
title = "Create a secure password to finish signing up."
uppercase_required = "Include at least one uppercase letter."
[msg.userfront.signup.password.rule]
lowercase = "Lowercase letter"
min_length = "At least {count} characters"
min_types = "At least {count} character types"
number = "Number"
symbol = "Symbol"
uppercase = "Uppercase letter"
[msg.userfront.signup.phone]
code_mismatch = "The verification code does not match."
send_failed = "Failed to send the SMS: {error}"
verified = "Phone number verified."
verify_failed = "Phone verification failed: {error}"
[msg.userfront.signup.policy]
loading = "Loading the password policy..."
lowercase = "Lowercase letter"
min_length = "At least {count} characters"
min_types = "At least {count} character types"
number = "Number"
summary = "Security policy: {rules}"
symbol = "Symbol"
uppercase = "Uppercase letter"
[msg.userfront.signup.profile]
affiliate_hint = "This will be selected automatically when you use an affiliate email."
title = "Tell us about your affiliation."
[msg.userfront.signup.success]
body = "Your account has been created successfully."
title = "Sign-up complete"
[ui.common]
add = "Add"
admin_only = "Admin Only"
all = "All"
assign = "Assign"
back = "Back"
back_to_login = "Back to login"
cancel = "Cancel"
change_file = "Change File"
clear_search = "Clear Search"
close = "Close"
collapse = "Collapse"
confirm = "Confirm"
copy = "Copy"
create = "Create"
delete = "Delete"
details = "Details"
edit = "Edit"
enabled = "Enabled"
export = "Export"
fail = "Fail"
generate = "ui.common.generate"
go_home = "Go Home"
hyphen = "-"
language = "Language"
language_en = "English"
language_ko = "Korean"
manage = "Manage"
na = "N/A"
never = "Never"
next = "Next"
none = "None"
page_of = "Page {page} of {total}"
prev = "Prev"
previous = "Previous"
qr = "QR"
read_only = "Read Only"
refresh = "Refresh"
remove = "Remove"
resend = "Resend"
reset = "Reset"
retry = "Retry"
save = "Save"
search = "Search"
select = "Select"
select_file = "Select File"
select_placeholder = "Select Placeholder"
show_more = "Show More"
success = "Success"
theme_dark = "Dark"
theme_light = "Light"
theme_toggle = "Theme Toggle"
unknown = "Unknown"
view = "View"
[ui.common.badge]
admin_only = "Admin only"
command_only = "Command only"
system = "System"
[ui.common.status]
active = "Active"
archived = "Archived"
baron_guest = "Baron Guest"
blocked = "ui.common.status.blocked"
extended_leave = "Extended Leave"
failure = "Failure"
inactive = "Inactive"
leave_of_absence = "Leave of absence"
ok = "Ok"
pending = "Pending"
preboarding = "Preboarding"
success = "Success"
suspended = "Suspended"
temporary_leave = "Temporary Leave"
[ui.userfront]
app_title = "Baron SW Portal"
[ui.userfront.app_label]
admin_console = "Admin Console"
baron = "Baron"
dev_console = "Dev Console"
[ui.userfront.audit]
[ui.userfront.audit.table]
action = "Action"
app = "App"
auth_method = "Auth Method"
browser = "Browser"
date = "Date"
device = "Device"
ip = "IP"
pending = "Pending"
result = "Result"
session_id = "Session ID"
status = "Status"
[ui.userfront.auth_method]
ory = "Ory"
session = "Session"
[ui.userfront.consent]
accept = "Agree and continue"
requested_scopes = "Requested permissions"
title = "Permission request"
[ui.userfront.consent.cancel]
confirm_button = "Yes, cancel"
title = "Cancel consent"
[ui.userfront.dashboard]
last_auth_label = "Last sign-in"
link_status_label = "Link status"
status_history = "Link details"
[ui.userfront.dashboard.activity]
linked = "Linked"
[ui.userfront.dashboard.sessions]
active_badge = "Active"
current_badge = "Current"
current_disabled = "Current session"
unknown_device = "Unknown device"
unknown_session = "Session"
[ui.userfront.dashboard.sessions.revoke]
action = "End session"
title = "End session"
[ui.userfront.dashboard.approved_session]
default = "Default"
userfront = "Approved UserFront session ID"
[ui.userfront.dashboard.revoke]
confirm_button = "Disconnect"
title = "Disconnect app"
[ui.userfront.dashboard.scopes]
title = "Consent scopes"
[ui.userfront.dashboard.status]
revoked = "Revoked"
[ui.userfront.device]
android = "Mobile(Android)"
ios = "Mobile(iOS)"
linux = "Desktop(Linux)"
macos = "Desktop(macOS)"
windows = "Desktop(Windows)"
[ui.userfront.error]
go_home = "Go Home"
go_login = "Go Login"
switch_account = "Sign in with another account"
[ui.userfront.forgot]
heading = "Forgot your password?"
input_label = "Email address or phone number"
submit = "Send reset link"
title = "Reset password"
[ui.userfront.login]
forgot_password = "Forgot Password"
signup = "Sign up"
[ui.userfront.login.action]
submit = "Sign in"
[ui.userfront.login.field]
login_id = "Email address or phone number"
password = "Password"
[ui.userfront.login.link]
action_label = "Go to sign-in"
code_only = "Get code only ({time})"
page_title = "Link sign-in"
resend_with_time = "Resend ({time})"
send = "Send"
title = "Link sign-in complete"
[ui.userfront.login.qr]
expired = "Expired"
refresh = "Refresh"
remaining = "Remaining: {time}"
[ui.userfront.login.short_code]
digits = "6 digits"
expire_time = "Expires in {time}"
prefix = "2 letters"
submit = "Sign in with code"
[ui.userfront.login.tabs]
link = "Sign-in link"
password = "Password"
qr = "QR Code"
[ui.userfront.login.unregistered]
action = "Create an account"
title = "Account not found"
[ui.userfront.login.verification]
action_label = "Done"
action_label_remote = "Go to sign-in window"
action_label_close = "Close Window"
page_title = "Baron SW Portal"
title = "Approval complete"
title_pending = "Checking approval"
title_remote = "Sign-in Approved"
[ui.userfront.login_success]
later = "Do this later (go to dashboard)"
qr = "Use QR approval"
title = "Sign-in complete"
[ui.userfront.nav]
dashboard = "Dashboard"
logout = "Logout"
profile = "Profile"
qr_scan = "QR Scan"
[ui.userfront.profile]
department_empty = "Department Empty"
manage = "Manage profile"
user_fallback = "User"
[ui.userfront.profile.field]
affiliation = "Affiliation"
company_code = "Company Code"
department = "Department"
email = "Email"
name = "Name"
tenant = "Tenant"
tenant_slug = "Tenant Slug"
[ui.userfront.profile.password]
change = "Change"
confirm = "Confirm"
current = "Current"
forgot = "Forgot"
new = "New"
title = "Change password"
[ui.userfront.profile.phone]
code_hint = "6-digit code"
request_code = "Send code"
title = "Phone number"
[ui.userfront.profile.section]
basic = "Basic"
organization = "Organization"
security = "Security"
[ui.userfront.qr]
request_permission = "Allow camera access"
rescan = "Rescan"
result_failure = "Approval failed"
result_success = "Approval complete"
title = "Scan QR Code"
[ui.userfront.reset]
confirm_password = "Confirm Password"
new_password = "New Password"
submit = "Submit"
subtitle = "Set a new password"
title = "Create a new password"
[ui.userfront.sections]
apps = "Apps"
audit = "Audit"
sessions = "Sessions"
[ui.userfront.session]
active = "Active session"
unknown = "Unknown"
[ui.userfront.signup]
complete = "Finish sign-up"
next_step = "Next"
title = "Sign up"
[ui.userfront.signup.agreement]
all = "Agree to all"
privacy_title = "Privacy Policy (Required)"
required = "Required"
tos_title = "Terms of Service (Required)"
[ui.userfront.signup.auth]
code_label = "6-digit verification code"
request_code = "Send code"
[ui.userfront.signup.auth.email]
label = "Email address"
title = "Email verification"
[ui.userfront.signup.password]
confirm_label = "Password Confirm"
label = "Password"
[ui.userfront.signup.phone]
label = "Phone number (no hyphens)"
title = "Phone verification"
[ui.userfront.signup.profile]
affiliation_type = "Affiliation Type"
company = "Company"
department = "Department"
department_optional = "Department (optional)"
name = "Name"
[ui.userfront.signup.steps]
agreement = "Terms"
password = "Password"
profile = "Profile"
verify = "Verification"
[ui.userfront.signup.success]
action = "Go to sign-in"
[ui.userfront.audit.filter]
title = "Manage My Activity"
toggle_label = "Show active sessions only"
[msg.userfront.audit.filter]
description = "Toggle to view only active sessions."

View File

@@ -0,0 +1,930 @@
[domain]
[domain.affiliation]
affiliate = "가족사 임직원"
general = "일반 사용자"
[domain.company]
baron = "바론"
halla = "한라"
hanmac = "한맥"
jangheon = "장헌"
ptc = "PTC"
saman = "삼안"
[domain.tenant_type]
company = "COMPANY (일반 기업)"
company_group = "COMPANY_GROUP (그룹사/지주사)"
organization = "ORGANIZATION (정규 조직)"
personal = "PERSONAL (개인 워크스페이스)"
user_group = "USER_GROUP (내부 부서/팀)"
[err.userfront]
[err.userfront.auth_proxy]
consent_accept = "동의 처리에 실패했습니다."
consent_fetch = "동의 정보를 가져오지 못했습니다."
consent_reject = "동의 거부에 실패했습니다."
linked_app_revoke = "연동 해지에 실패했습니다."
login_failed = "로그인에 실패했습니다."
oidc_accept = "OIDC 로그인 승인에 실패했습니다."
password_reset_complete = "비밀번호 재설정에 실패했습니다."
password_reset_init = "비밀번호 재설정을 시작하지 못했습니다."
[err.userfront.profile]
load_failed = "프로필을 불러오지 못했습니다: {error}"
password_change_failed = "비밀번호 변경에 실패했습니다: {error}"
send_code_failed = "인증번호 전송 실패: {error}"
update_failed = "프로필 업데이트에 실패했습니다: {error}"
verify_code_failed = "인증 실패: {error}"
[err.userfront.session]
missing = "활성 세션이 없습니다."
[msg.userfront.audit]
browser = "브라우저: {value}"
date = "접속일자: {value}"
device = "접속환경: {value}"
end = "더 이상 항목이 없습니다."
filtered_empty = "활성 세션으로 필터링된 접속 이력이 없습니다."
ip = "접속 IP: {value}"
load_more_error = "더 불러오지 못했습니다."
result = "인증결과: {value}"
session_id = "Session ID: {value}"
status = "현황: (준비중)"
[msg.userfront.dashboard]
approved_device = "승인 기기: {device}"
approved_ip = "승인 IP: {ip}"
audit_empty = "최근 접속 이력이 없습니다."
audit_load_error = "접속이력을 불러오지 못했습니다."
auth_method = "인증수단: {method}"
client_id = "Client ID: {id}"
client_id_missing = "Client ID 없음"
current_status = "현재 상태: {status}"
last_auth = "최근 인증: {value}"
link_status = "연동 상태: {status}"
link_missing = "이동할 페이지 주소(Client URI)가 설정되지 않았습니다."
link_open_error = "해당 링크를 열 수 없습니다."
render_error = "대시보드 렌더링 오류: {error}"
session_id_copied = "세션 ID가 복사되었습니다."
[msg.userfront.error]
detail_contact = "관리자에게 문의해 주세요."
detail_generic = "오류가 발생했습니다."
detail_request = "요청을 처리하는 중 문제가 발생했습니다."
id = "오류 ID: {id}"
title = "인증 과정에서 오류가 발생했습니다"
title_generic = "오류가 발생했습니다"
title_with_code = "오류: {code}"
type = "오류 종류: {type}"
[msg.userfront.error.tenant]
account = "계정"
account_unknown = "알 수 없음"
affiliated_tenants = "전체 소속 테넌트"
allowed_box_title = "접속 가능 테넌트"
allowed_tenants = "접속 가능 테넌트"
detail = "현재 로그인된 계정은 이 애플리케이션에 접근할 수 없습니다."
load_failed = "계정 정보를 확인하지 못했습니다. 다시 시도해 주세요."
loading = "현재 계정 정보를 불러오는 중입니다."
lookup_fallback = "표시 정보가 충분하지 않아 일부 항목은 확인되지 않을 수 있습니다."
page_title = "애플리케이션 접근이 제한되었습니다"
primary_tenant = "대표 소속 테넌트"
tenant = "소속 테넌트"
tenant_unknown = "알 수 없음"
title = "접근 제한 정보"
[msg.userfront.forgot]
description = "계정과 연결된 이메일 주소 또는 휴대폰 번호를 입력하시면, 비밀번호를 재설정할 수 있는 링크를 보내드립니다."
dry_send = "drySend 모드: 실제 이메일/SMS는 발송되지 않습니다."
error = "전송에 실패했습니다: {error}"
input_required = "이메일 또는 휴대폰 번호를 입력해주세요."
sent = "비밀번호 재설정 링크가 전송되었습니다. 이메일 또는 SMS를 확인해주세요."
[msg.userfront.login]
cookie_check_failed = "로그인 확인 실패: {error}"
dry_send = "drySend 모드: 실제 이메일/SMS는 발송되지 않습니다."
link_failed = "오류: {error}"
link_send_failed = "전송 실패: {error}"
link_sent_email = "입력하신 이메일로 로그인 링크를 보냈습니다."
link_sent_phone = "입력하신 번호로 로그인 링크를 보냈습니다."
link_timeout = "시간이 경과되었습니다."
no_account = "계정이 없으신가요?"
oidc_failed = "OIDC 로그인 처리에 실패했습니다. 다시 시도해 주세요."
qr_expired = "시간이 경과되었습니다."
qr_init_failed = "QR 초기화에 실패했습니다: {error}"
qr_login_required = "로그인 한 상태여야 QR 스캔으로 로그인 할 수 있습니다"
token_missing = "로그인 토큰을 확인할 수 없습니다."
verification_failed = "승인 처리에 실패했습니다: {error}"
[msg.userfront.login_success]
subtitle = "성공적으로 로그인되었습니다."
[msg.userfront.consent]
accept_error = "동의 처리에 실패했습니다: {error}"
client_id = "클라이언트 ID: {id}"
client_unknown = "알 수 없는 앱"
description = "아래 서비스가 회원님의 계정 정보에 접근하려고 합니다.\n계속 진행하려면 동의 여부를 선택해 주세요."
load_error = "동의 정보를 불러오는데 실패했습니다: {error}"
missing_redirect = "동의가 처리되었으나 리다이렉트 URL을 받지 못했습니다."
redirect_notice = "동의 후 자동으로 서비스로 이동합니다."
scope_count = "총 {count}개"
[msg.userfront.profile]
department_missing = "소속 정보 없음"
department_required = "소속을 입력해주세요."
email_missing = "이메일 없음"
greeting = "안녕하세요, {name}님"
load_failed = "정보를 불러올 수 없습니다."
name_missing = "이름 없음"
name_required = "이름을 입력해주세요."
phone_required = "휴대폰 번호를 입력해주세요."
phone_verify_required = "휴대폰 번호 인증이 필요합니다."
update_failed = "수정 실패: {error}"
update_success = "정보가 수정되었습니다."
[msg.userfront.qr]
camera_error = "카메라 오류: {error}"
permission_error = "카메라 권한 요청에 실패했습니다. 브라우저/OS 설정을 확인해주세요."
permission_required = "카메라 권한이 필요합니다."
[msg.userfront.reset]
invalid_body = "비밀번호 재설정 링크가 만료되었거나 잘못되었습니다. 다시 시도해주세요."
invalid_link = "유효하지 않은 재설정 링크입니다. (loginId/token 누락)"
invalid_title = "유효하지 않은 링크입니다."
policy_loading = "비밀번호 정책을 불러오는 중입니다..."
success = "비밀번호가 성공적으로 변경되었습니다. 다시 로그인해주세요."
[msg.userfront.sections]
apps_subtitle = "현재 연결된 앱과 최근 인증 상태입니다."
audit_subtitle = "Baron 로그인 기준의 최근 접근 기록입니다."
sessions_subtitle = "현재 로그인된 기기와 브라우저 세션입니다."
[msg.userfront.settings]
disabled = "현재 계정 설정 화면은 준비 중입니다."
[msg.userfront.signup]
failed = "가입 실패: {error}"
privacy_full = "개인정보 수집 및 이용 동의 전문..."
tos_full = "서비스 이용약관 전문..."
[ui.common.badge]
admin_only = "Admin only"
command_only = "Command only"
system = "System"
[ui.common.status]
active = "활성"
blocked = "차단됨"
failure = "실패"
inactive = "비활성"
ok = "정상"
pending = "준비 중"
success = "성공"
[ui.userfront.app_label]
admin_console = "Admin Console"
baron = "Baron 로그인"
dev_console = "Dev Console"
[ui.userfront.auth_method]
ory = "Ory 세션"
session = "세션"
[ui.userfront.dashboard]
last_auth_label = "최근 인증"
link_status_label = "연동 상태"
status_history = "연동 정보"
[ui.userfront.device]
android = "Mobile(Android)"
ios = "Mobile(iOS)"
linux = "Desktop(Linux)"
macos = "Desktop(macOS)"
windows = "Desktop(Windows)"
[ui.userfront.error]
go_home = "홈으로 이동"
go_login = "로그인으로 이동"
switch_account = "다른 계정으로 로그인"
[ui.userfront.forgot]
heading = "비밀번호를 잊으셨나요?"
input_label = "이메일 또는 휴대폰 번호"
submit = "재설정 링크 전송"
title = "비밀번호 재설정"
[ui.userfront.login]
forgot_password = "비밀번호를 잊으셨나요?"
signup = "회원가입"
[ui.userfront.login_success]
later = "나중에 하기 (대시보드로 이동)"
qr = "QR 인증 (카메라 켜기)"
title = "로그인 완료"
[ui.userfront.consent]
accept = "동의하고 계속하기"
requested_scopes = "요청된 권한"
title = "접근 권한 요청"
[ui.userfront.nav]
dashboard = "대시보드"
logout = "로그아웃"
profile = "내 정보"
qr_scan = "QR 스캔"
[ui.userfront.profile]
department_empty = "소속 정보 없음"
manage = "프로필 관리"
user_fallback = "사용자"
[ui.userfront.qr]
rescan = "다시 스캔"
result_success = "승인 완료"
title = "Scan QR Code"
[ui.userfront.reset]
confirm_password = "새 비밀번호 확인"
new_password = "새 비밀번호"
submit = "비밀번호 변경"
subtitle = "새로운 비밀번호 설정"
title = "새 비밀번호 설정"
[ui.userfront.sections]
apps = "나의 App 현황"
audit = "접속이력"
sessions = "활성 세션"
[ui.userfront.session]
active = "세션 활성"
unknown = "알 수 없음"
[ui.userfront.signup]
complete = "가입 완료"
next_step = "다음 단계"
title = "회원가입"
[msg.userfront]
greeting = "안녕하세요, {name}님"
[msg.userfront.audit]
browser = "브라우저: {value}"
date = "접속일자: {value}"
device = "접속환경: {value}"
end = "더 이상 항목이 없습니다."
filtered_empty = "활성 세션으로 필터링된 접속 이력이 없습니다."
ip = "접속 IP: {value}"
load_more_error = "더 불러오지 못했습니다."
result = "인증결과: {value}"
session_id = "Session ID: {value}"
status = "현황: (준비중)"
[msg.userfront.consent]
accept_error = "동의 처리에 실패했습니다: {error}"
client_id = "클라이언트 ID: {id}"
client_unknown = "알 수 없는 앱"
description = "아래 서비스가 회원님의 계정 정보에 접근하려고 합니다.\\\\n계속 진행하려면 동의 여부를 선택해 주세요."
load_error = "동의 정보를 불러오는데 실패했습니다: {error}"
missing_redirect = "동의가 처리되었으나 리다이렉트 URL을 받지 못했습니다."
redirect_notice = "동의 후 자동으로 서비스로 이동합니다."
scope_count = "총 {count}개"
[msg.userfront.consent.cancel]
confirm = "권한 동의를 취소하면 해당 서비스를 이용할 수 없습니다. 취소하시겠습니까?"
error = "취소 처리 중 오류가 발생했습니다: {error}"
[msg.userfront.consent.scope]
email = "이메일 주소 (계정 식별 및 알림 용도)"
offline_access = "오프라인 접근 (로그인 유지)"
openid = "OpenID 인증 정보 (로그인 상태 확인)"
phone = "휴대폰 번호 (본인 인증 및 알림)"
profile = "기본 프로필 정보 (이름, 사용자 식별자)"
[msg.userfront.dashboard]
approved_device = "승인 기기: {device}"
approved_ip = "승인 IP: {ip}"
audit_empty = "최근 접속 이력이 없습니다."
audit_load_error = "접속이력을 불러오지 못했습니다."
auto_login_supported = "연동앱 클릭 시 별도 로그인 없이 로그인할 수 있습니다."
auth_method = "인증수단: {method}"
client_id = "Client ID: {id}"
client_id_missing = "Client ID 없음"
current_status = "현재 상태: {status}"
last_auth = "최근 인증: {value}"
link_missing = "이동할 페이지 주소(Client URI)가 설정되지 않았습니다."
link_open_error = "해당 링크를 열 수 없습니다."
render_error = "대시보드 렌더링 오류: {error}"
session_id_copied = "세션 ID가 복사되었습니다."
[msg.userfront.dashboard.activities]
empty = "연동된 앱이 없습니다."
empty_detail = "앱을 연동하면 최근 활동과 상태가 표시됩니다."
error = "연동 정보를 불러오지 못했습니다."
[msg.userfront.dashboard.sessions]
browser = "브라우저: {value}"
empty = "활성 세션이 없습니다."
empty_detail = "같은 계정으로 로그인한 기기가 여기에 표시됩니다."
error = "세션 정보를 불러오지 못했습니다."
os = "OS: {value}"
recent_app = "최근 접속 앱: {app}"
session_id = "세션 ID: {id}"
[msg.userfront.dashboard.sessions.revoke]
confirm = "{target} 세션을 종료하시겠습니까?\n대상 기기에서는 다시 로그인이 필요합니다."
error = "세션 종료 실패: {error}"
success = "세션이 종료되었습니다."
[msg.userfront.dashboard.approved_session]
copy_click = "{label}: {id}\\\\n클릭하면 복사됩니다."
copy_tap = "{label}: {id}\\\\n탭하면 복사됩니다."
none = "{label} 없음"
[msg.userfront.dashboard.revoke]
confirm = "{app} 앱과의 연동을 해지하시겠습니까?\\\\n해지하면 다음 로그인 시 다시 동의가 필요합니다."
error = "해지 실패: {error}"
success = "{app} 연동이 해지되었습니다."
[msg.userfront.dashboard.scopes]
empty = "요청된 권한이 없습니다."
[msg.userfront.dashboard.timeline]
load_error = "접속이력을 불러오지 못했습니다."
[msg.userfront.error]
detail_contact = "관리자에게 문의해 주세요."
detail_generic = "오류가 발생했습니다."
detail_request = "요청을 처리하는 중 문제가 발생했습니다."
id = "오류 ID: {id}"
title = "인증 과정에서 오류가 발생했습니다"
title_generic = "오류가 발생했습니다"
title_with_code = "오류: {code}"
type = "오류 종류: {type}"
[msg.userfront.error.tenant]
account = "계정"
account_unknown = "알 수 없음"
affiliated_tenants = "전체 소속 테넌트"
allowed_box_title = "접속 가능 테넌트"
allowed_tenants = "접속 가능 테넌트"
detail = "현재 로그인된 계정은 이 애플리케이션에 접근할 수 없습니다."
load_failed = "계정 정보를 확인하지 못했습니다. 다시 시도해 주세요."
loading = "현재 계정 정보를 불러오는 중입니다."
lookup_fallback = "표시 정보가 충분하지 않아 일부 항목은 확인되지 않을 수 있습니다."
page_title = "애플리케이션 접근이 제한되었습니다"
primary_tenant = "대표 소속 테넌트"
tenant = "소속 테넌트"
tenant_unknown = "알 수 없음"
title = "접근 제한 정보"
[msg.userfront.error.ory]
"$normalizedCode" = "{error}"
access_denied = "사용자가 동의를 거부했습니다."
consent_required = "앱 접근 동의가 필요합니다."
interaction_required = "추가 상호작용이 필요합니다. 다시 시도해 주세요."
invalid_client = "클라이언트 인증 정보가 유효하지 않습니다."
invalid_grant = "인증 요청이 만료되었거나 유효하지 않습니다."
invalid_request = "잘못된 요청입니다."
invalid_scope = "요청한 권한 범위가 유효하지 않습니다."
login_required = "로그인이 필요합니다."
request_forbidden = "요청이 거부되었습니다."
server_error = "인증 서버 오류가 발생했습니다."
temporarily_unavailable = "인증 서버를 일시적으로 사용할 수 없습니다."
unauthorized_client = "해당 클라이언트는 이 요청을 수행할 수 없습니다."
unsupported_response_type = "지원하지 않는 응답 타입입니다."
[msg.userfront.error.whitelist]
"$normalizedCode" = "{error}"
bad_request = "입력값을 확인해 주세요."
invalid_session = "세션이 만료되었습니다. 다시 로그인해 주세요."
not_found = "요청한 페이지를 찾을 수 없습니다."
password_or_email_mismatch = "이메일 혹은 비밀번호가 일치하지 않습니다."
rate_limited = "요청이 많습니다. 잠시 후 다시 시도해 주세요."
recovery_expired = "재설정 링크가 만료되었습니다. 다시 요청해 주세요."
recovery_invalid = "재설정 링크가 유효하지 않습니다."
settings_disabled = "현재 계정 설정 화면은 준비 중입니다."
verification_required = "추가 인증이 필요합니다. 안내에 따라 진행해 주세요."
[msg.userfront.forgot]
description = "계정과 연결된 이메일 주소 또는 휴대폰 번호를 입력하시면, 비밀번호를 재설정할 수 있는 링크를 보내드립니다."
dry_send = "drySend 모드: 실제 이메일/SMS는 발송되지 않습니다."
error = "전송에 실패했습니다: {error}"
input_required = "이메일 또는 휴대폰 번호를 입력해주세요."
sent = "비밀번호 재설정 링크가 전송되었습니다. 이메일 또는 SMS를 확인해주세요."
[msg.userfront.login]
cookie_check_failed = "로그인 확인 실패: {error}"
dry_send = "drySend 모드: 실제 이메일/SMS는 발송되지 않습니다."
link_failed = "오류: {error}"
link_send_failed = "전송 실패: {error}"
link_sent_email = "입력하신 이메일로 로그인 링크를 보냈습니다."
link_sent_phone = "입력하신 번호로 로그인 링크를 보냈습니다."
link_timeout = "시간이 경과되었습니다."
no_account = "계정이 없으신가요?"
oidc_failed = "OIDC 로그인 처리에 실패했습니다. 다시 시도해 주세요."
qr_expired = "시간이 경과되었습니다."
qr_init_failed = "QR 초기화에 실패했습니다: {error}"
qr_login_required = "로그인 한 상태여야 QR 스캔으로 로그인 할 수 있습니다"
token_missing = "로그인 토큰을 확인할 수 없습니다."
verification_failed = "승인 처리에 실패했습니다: {error}"
[msg.userfront.login.link]
approved = "msg.userfront.login.link.approved"
helper = "입력하신 정보로 로그인 링크를 전송합니다."
missing_login_id = "이메일 또는 휴대폰 번호를 입력해 주세요."
missing_phone = "휴대폰 번호를 입력해 주세요."
resend_wait = "재발송은 {time} 후 가능합니다."
short_code_help = "링크로 받은 값의 뒤 문자 2개와 숫자 6자리를 입력하셔도 로그인 할 수 있습니다."
[msg.userfront.login.password]
failed = "로그인 실패: {error}"
missing_credentials = "이메일(또는 전화번호)와 비밀번호를 모두 입력해주세요."
[msg.userfront.login.qr]
load_failed = "QR 코드를 불러오지 못했습니다."
scan_hint = "모바일 앱으로 스캔하세요"
[msg.userfront.login.short_code]
invalid = "문자 2개와 숫자 6자리를 입력해 주세요."
[msg.userfront.login.unregistered]
body = "가입되지 않은 정보입니다.\\\\n회원가입 후 이용해 주세요."
[msg.userfront.login.verification]
approved = "승인되었습니다. 로그인은 요청하신 창에서 완료됩니다."
approved_local = "승인 되었습니다. 이 기기는 로그인되어 있는 상태입니다. 원격 창도 로그인이 될 예정입니다"
approved_remote = "요청하신 로그인이 완료되었습니다"
pending_remote = "승인 요청을 확인하고 있습니다. 잠시만 기다려 주세요."
close_hint = "이 창은 이제 닫으셔도 됩니다."
success = "로그인 승인에 성공했습니다."
[msg.userfront.login_success]
subtitle = "성공적으로 로그인되었습니다."
[msg.userfront.profile]
department_missing = "소속 정보 없음"
department_required = "소속을 입력해주세요."
email_missing = "이메일 없음"
greeting = "안녕하세요, {name}님"
load_failed = "정보를 불러올 수 없습니다."
name_missing = "이름 없음"
name_required = "이름을 입력해주세요."
phone_required = "휴대폰 번호를 입력해주세요."
phone_verify_required = "휴대폰 번호 인증이 필요합니다."
update_failed = "수정 실패: {error}"
update_success = "정보가 수정되었습니다."
[msg.userfront.profile.password]
change_failed = "비밀번호 변경 실패: {error}"
changed = "비밀번호가 변경되었습니다."
current_required = "현재 비밀번호를 입력해 주세요."
mismatch = "새 비밀번호가 일치하지 않습니다."
new_required = "새 비밀번호를 입력해 주세요."
subtitle = "현재 비밀번호 확인 후 새 비밀번호로 변경합니다."
[msg.userfront.profile.phone]
code_sent = "인증번호가 전송되었습니다."
send_failed = "전송 실패: {error}"
verified = "인증되었습니다."
verify_failed = "인증 실패: {error}"
verify_notice = "휴대폰 번호를 변경하려면 SMS 인증이 필요합니다."
[msg.userfront.profile.section]
basic = "계정 기본 정보를 관리합니다."
organization = "소속 및 구분 정보입니다."
security = "비밀번호를 안전하게 관리합니다."
[msg.userfront.qr]
approve_error = "msg.userfront.qr.approve_error"
approve_success = "msg.userfront.qr.approve_success"
camera_error = "카메라 오류: {error}"
permission_error = "카메라 권한 요청에 실패했습니다. 브라우저/OS 설정을 확인해주세요."
permission_required = "카메라 권한이 필요합니다."
[msg.userfront.reset]
invalid_body = "비밀번호 재설정 링크가 만료되었거나 잘못되었습니다. 다시 시도해주세요."
invalid_link = "유효하지 않은 재설정 링크입니다. (loginId/token 누락)"
invalid_title = "유효하지 않은 링크입니다."
policy_loading = "비밀번호 정책을 불러오는 중입니다..."
success = "비밀번호가 성공적으로 변경되었습니다. 다시 로그인해주세요."
[msg.userfront.reset.error]
empty_password = "비밀번호를 입력해주세요."
generic = "비밀번호 변경에 실패했습니다: {error}"
lowercase = "최소 1개 이상의 소문자를 포함해야 합니다."
min_length = "비밀번호는 최소 {count}자 이상이어야 합니다."
min_types = "비밀번호는 영문 대/소문자/숫자/특수문자 중 {count}가지 이상 포함해야 합니다."
mismatch = "비밀번호가 일치하지 않습니다."
number = "최소 1개 이상의 숫자를 포함해야 합니다."
symbol = "최소 1개 이상의 특수문자를 포함해야 합니다."
uppercase = "최소 1개 이상의 대문자를 포함해야 합니다."
[msg.userfront.reset.policy]
lowercase = "소문자 1개 이상"
min_length = "최소 {count}자 이상"
min_types = "영문 대/소문자/숫자/특수문자 중 {count}가지 이상"
number = "숫자 1개 이상"
symbol = "특수문자 1개 이상"
uppercase = "대문자 1개 이상"
[msg.userfront.sections]
apps_subtitle = "현재 연결된 앱과 최근 인증 상태입니다."
audit_subtitle = "Baron 로그인 기준의 최근 접근 기록입니다."
[msg.userfront.settings]
disabled = "현재 계정 설정 화면은 준비 중입니다."
[msg.userfront.signup]
failed = "가입 실패: {error}"
privacy_full = "개인정보 수집 및 이용 동의 전문..."
tos_full = "서비스 이용약관 전문..."
[msg.userfront.signup.agreement]
all_hint = "필수 약관 2개를 모두 확인하고 동의하면 다음 단계로 진행할 수 있습니다."
description = "계속 진행하려면 서비스 이용 조건과 개인정보 수집·이용 항목을 확인한 뒤 동의해주세요."
privacy_summary = "개인정보 수집 항목, 이용 목적, 보관 기준을 안내합니다."
progress = "필수 약관 {total}개 중 {count}개 동의 완료"
title = "서비스 이용을 위해\\\\n약관에 동의해주세요"
tos_summary = "서비스 이용 조건과 책임 범위를 확인할 수 있습니다."
[msg.userfront.signup.auth]
affiliate_notice = "가족사 회원의 경우 반드시 회사 공식 이메일을 입력해주세요."
title = "본인 확인을 위해\\\\n인증을 진행해주세요"
[msg.userfront.signup.email]
code_mismatch = "인증코드가 일치하지 않습니다."
duplicate = "이미 가입된 이메일입니다."
invalid = "유효한 이메일 형식이 아닙니다."
send_failed = "발송 실패: {error}"
verified = "✅ 이메일 인증 완료"
verify_failed = "인증 실패: {error}"
[msg.userfront.signup.password]
length_required = "비밀번호는 최소 12자 이상이어야 합니다."
lowercase_required = "소문자가 최소 1개 이상 포함되어야 합니다."
mismatch = "비밀번호가 일치하지 않습니다."
number_required = "숫자가 최소 1개 이상 포함되어야 합니다."
symbol_required = "특수문자가 최소 1개 이상 포함되어야 합니다."
title = "마지막으로\\\\n비밀번호를 설정해주세요"
uppercase_required = "대문자가 최소 1개 이상 포함되어야 합니다."
[msg.userfront.signup.password.rule]
lowercase = "소문자"
min_length = "{count}자 이상"
min_types = "문자 유형 {count}가지 이상"
number = "숫자"
symbol = "특수문자"
uppercase = "대문자"
[msg.userfront.signup.phone]
code_mismatch = "인증코드가 일치하지 않습니다."
send_failed = "발송 실패: {error}"
verified = "✅ 휴대폰 인증 완료"
verify_failed = "인증 실패: {error}"
[msg.userfront.signup.policy]
loading = "비밀번호 정책을 불러오는 중입니다..."
lowercase = "소문자"
min_length = "최소 {count}자 이상"
min_types = "영문 대/소문자/숫자/특수문자 중 {count}가지 이상"
number = "숫자"
summary = "보안 정책: {rules}"
symbol = "특수문자"
uppercase = "대문자"
[msg.userfront.signup.profile]
affiliate_hint = "가족사 이메일 사용 시 자동으로 선택됩니다."
title = "회원님의\\\\n소속 정보를 알려주세요"
[msg.userfront.signup.success]
body = "성공적으로 가입되었습니다."
title = "회원가입 완료"
[ui.common]
add = "추가"
admin_only = "관리자 전용"
all = "전체"
assign = "할당"
back = "돌아가기"
back_to_login = "로그인으로 돌아가기"
cancel = "취소"
change_file = "파일 변경"
clear_search = "검색 초기화"
close = "닫기"
collapse = "접기"
confirm = "확인"
copy = "복사"
create = "생성"
delete = "삭제"
details = "상세정보"
edit = "편집"
enabled = "사용"
export = "내보내기"
fail = "실패"
generate = "ui.common.generate"
go_home = "홈으로"
hyphen = "-"
language = "언어"
language_en = "English"
language_ko = "한국어"
manage = "관리"
na = "N/A"
never = "Never"
next = "다음"
none = "없음"
page_of = "Page {page} of {total}"
prev = "이전"
previous = "이전"
qr = "QR"
read_only = "읽기 전용"
refresh = "새로고침"
remove = "제외"
resend = "재발송"
reset = "초기화"
retry = "다시 시도"
save = "저장"
search = "검색"
select = "선택"
select_file = "파일 선택"
select_placeholder = "선택하세요"
show_more = "+ 더보기"
success = "성공"
theme_dark = "Dark"
theme_light = "Light"
theme_toggle = "테마 전환"
unknown = "Unknown"
view = "보기"
[ui.common.badge]
admin_only = "Admin only"
command_only = "Command only"
system = "System"
[ui.common.status]
active = "활성"
archived = "보관됨"
baron_guest = "Baron 게스트"
blocked = "ui.common.status.blocked"
extended_leave = "장기휴직"
failure = "실패"
inactive = "비활성"
leave_of_absence = "휴직"
ok = "정상"
pending = "준비 중"
preboarding = "입사대기"
success = "성공"
suspended = "정지"
temporary_leave = "단기휴무"
[ui.userfront]
app_title = "Baron SW 포탈"
[ui.userfront.app_label]
admin_console = "Admin Console"
baron = "Baron 로그인"
dev_console = "Dev Console"
[ui.userfront.audit]
[ui.userfront.audit.table]
action = "관리"
app = "애플리케이션"
auth_method = "인증수단"
browser = "브라우저"
date = "접속일자"
device = "접속환경"
ip = "IP"
pending = "(준비중)"
result = "인증결과"
session_id = "Session ID"
status = "현황"
[ui.userfront.auth_method]
ory = "Ory 세션"
session = "세션"
[ui.userfront.consent]
accept = "동의하고 계속하기"
requested_scopes = "요청된 권한"
title = "접근 권한 요청"
[ui.userfront.consent.cancel]
confirm_button = "예, 취소합니다"
title = "동의 취소"
[ui.userfront.dashboard]
last_auth_label = "최근 인증"
status_history = "상태 이력"
[ui.userfront.dashboard.activity]
linked = "연동됨"
[ui.userfront.dashboard.sessions]
active_badge = "활성화"
current_badge = "접속중"
current_disabled = "현재 세션"
unknown_device = "알 수 없는 기기"
unknown_session = "세션 정보"
[ui.userfront.dashboard.sessions.revoke]
action = "세션 종료"
title = "세션 종료"
[ui.userfront.dashboard.approved_session]
default = "승인한 세션 ID"
userfront = "승인한 Userfront 세션 ID"
[ui.userfront.dashboard.revoke]
confirm_button = "해지하기"
title = "연동 해지"
[ui.userfront.dashboard.scopes]
title = "동의 범위"
[ui.userfront.dashboard.status]
revoked = "해지됨"
[ui.userfront.device]
android = "Mobile(Android)"
ios = "Mobile(iOS)"
linux = "Desktop(Linux)"
macos = "Desktop(macOS)"
windows = "Desktop(Windows)"
[ui.userfront.error]
go_home = "홈으로 이동"
go_login = "로그인으로 이동"
switch_account = "다른 계정으로 로그인"
[ui.userfront.forgot]
heading = "비밀번호를 잊으셨나요?"
input_label = "이메일 또는 휴대폰 번호"
submit = "재설정 링크 전송"
title = "비밀번호 재설정"
[ui.userfront.login]
forgot_password = "비밀번호를 잊으셨나요?"
signup = "회원가입"
[ui.userfront.login.action]
submit = "로그인"
[ui.userfront.login.field]
login_id = "이메일 또는 휴대폰 번호"
password = "비밀번호"
[ui.userfront.login.link]
action_label = "ui.userfront.login.link.action_label"
code_only = "코드만 받기({time})"
page_title = "ui.userfront.login.link.page_title"
resend_with_time = "재발송 ({time})"
send = "로그인 링크 전송"
title = "ui.userfront.login.link.title"
[ui.userfront.login.qr]
expired = "QR 코드 만료됨"
refresh = "QR 코드 새로고침"
remaining = "남은 시간: {time}"
[ui.userfront.login.short_code]
digits = "숫자 6자리"
expire_time = "유효시간 {time}"
prefix = "영문 2자리"
submit = "코드로 로그인"
[ui.userfront.login.tabs]
link = "로그인 링크"
password = "비밀번호"
qr = "QR 코드"
[ui.userfront.login.unregistered]
action = "회원가입 하기"
title = "미등록 회원"
[ui.userfront.login.verification]
action_label = "확인"
action_label_remote = "로그인 창으로 이동하기"
page_title = "Baron SW 포탈"
title = "승인 완료"
action_label_close = "창 닫기"
title_pending = "로그인 승인 확인 중"
title_remote = "로그인 승인 완료"
[ui.userfront.login_success]
later = "나중에 하기 (대시보드로 이동)"
qr = "QR 인증 (카메라 켜기)"
title = "로그인 완료"
[ui.userfront.nav]
dashboard = "대시보드"
logout = "로그아웃"
profile = "내 정보"
qr_scan = "QR 스캔"
[ui.userfront.profile]
department_empty = "소속 정보 없음"
manage = "프로필 관리"
user_fallback = "사용자"
[ui.userfront.profile.field]
affiliation = "구분"
company_code = "회사코드"
department = "소속"
email = "이메일"
name = "이름"
tenant = "소속 테넌트"
tenant_slug = "테넌트 Slug"
[ui.userfront.profile.password]
change = "비밀번호 변경"
confirm = "새 비밀번호 확인"
current = "현재 비밀번호"
forgot = "비밀번호를 잊으셨나요?"
new = "새 비밀번호"
title = "비밀번호 변경"
[ui.userfront.profile.phone]
code_hint = "인증번호 6자리"
request_code = "인증요청"
title = "전화번호"
[ui.userfront.profile.section]
basic = "기본 정보"
organization = "조직 정보"
security = "보안"
[ui.userfront.qr]
request_permission = "ui.userfront.qr.request_permission"
rescan = "다시 스캔"
result_failure = "ui.userfront.qr.result_failure"
result_success = "승인 완료"
title = "Scan QR Code"
[ui.userfront.reset]
confirm_password = "새 비밀번호 확인"
new_password = "새 비밀번호"
submit = "비밀번호 변경"
subtitle = "새로운 비밀번호 설정"
title = "새 비밀번호 설정"
[ui.userfront.sections]
apps = "나의 App 현황"
audit = "접속이력"
[ui.userfront.session]
active = "세션 활성"
unknown = "알 수 없음"
[ui.userfront.signup]
complete = "가입 완료"
next_step = "다음 단계"
title = "회원가입"
[ui.userfront.signup.agreement]
all = "모두 동의합니다"
privacy_title = "개인정보 수집 및 이용 동의 (필수)"
required = "필수"
tos_title = "바론 소프트웨어 이용약관 (필수)"
[ui.userfront.signup.auth]
code_label = "인증코드 6자리"
request_code = "인증요청"
[ui.userfront.signup.auth.email]
label = "이메일 주소"
title = "이메일 인증"
[ui.userfront.signup.password]
confirm_label = "비밀번호 확인"
label = "비밀번호"
[ui.userfront.signup.phone]
label = "휴대폰 번호 (-없이)"
title = "휴대폰 인증"
[ui.userfront.signup.profile]
affiliation_type = "소속 유형"
company = "가족사 선택"
department = "부서명"
department_optional = "소속 정보 (선택)"
name = "이름"
[ui.userfront.signup.steps]
agreement = "약관동의"
password = "비밀번호"
profile = "정보입력"
verify = "본인인증"
[ui.userfront.signup.success]
action = "로그인하기"
[ui.userfront.audit.filter]
title = "내 활동 관리"
toggle_label = "활성 세션만 보기"
[msg.userfront.audit.filter]
description = "활성화된 세션만 보려면 토글을 켜주세요."

View File

@@ -0,0 +1,902 @@
[domain]
[domain.affiliation]
affiliate = ""
general = ""
[domain.company]
baron = ""
halla = ""
hanmac = ""
jangheon = ""
ptc = ""
saman = ""
[domain.tenant_type]
company = ""
company_group = ""
organization = ""
personal = ""
user_group = ""
[err.userfront]
[err.userfront.auth_proxy]
consent_accept = ""
consent_fetch = ""
consent_reject = ""
linked_app_revoke = ""
login_failed = ""
oidc_accept = ""
password_reset_complete = ""
password_reset_init = ""
[err.userfront.profile]
load_failed = ""
password_change_failed = ""
send_code_failed = ""
update_failed = ""
verify_code_failed = ""
[err.userfront.session]
missing = ""
[msg.userfront.error]
detail_contact = ""
detail_generic = ""
detail_request = ""
id = ""
title = ""
title_generic = ""
title_with_code = ""
type = ""
[msg.userfront.error.tenant]
account = ""
account_unknown = ""
affiliated_tenants = ""
allowed_box_title = ""
allowed_tenants = ""
detail = ""
load_failed = ""
loading = ""
lookup_fallback = ""
page_title = ""
primary_tenant = ""
tenant = ""
tenant_unknown = ""
title = ""
[msg.userfront.forgot]
description = ""
dry_send = ""
error = ""
input_required = ""
sent = ""
[msg.userfront.login]
cookie_check_failed = ""
dry_send = ""
link_failed = ""
link_send_failed = ""
link_sent_email = ""
link_sent_phone = ""
link_timeout = ""
no_account = ""
oidc_failed = ""
qr_expired = ""
qr_init_failed = ""
qr_login_required = ""
token_missing = ""
verification_failed = ""
[msg.userfront.login_success]
subtitle = ""
[msg.userfront.consent]
accept_error = ""
client_id = ""
client_unknown = ""
description = ""
load_error = ""
missing_redirect = ""
redirect_notice = ""
scope_count = ""
[msg.userfront.profile]
department_missing = ""
department_required = ""
email_missing = ""
greeting = ""
load_failed = ""
name_missing = ""
name_required = ""
phone_required = ""
phone_verify_required = ""
update_failed = ""
update_success = ""
[msg.userfront.qr]
camera_error = ""
permission_error = ""
permission_required = ""
[msg.userfront.reset]
invalid_body = ""
invalid_link = ""
invalid_title = ""
policy_loading = ""
success = ""
[msg.userfront.sections]
apps_subtitle = ""
audit_subtitle = ""
sessions_subtitle = ""
[msg.userfront.settings]
disabled = ""
[msg.userfront.signup]
failed = ""
privacy_full = ""
tos_full = ""
[ui.common.badge]
admin_only = ""
command_only = ""
system = ""
[ui.common.status]
active = ""
blocked = ""
failure = ""
inactive = ""
ok = ""
pending = ""
success = ""
[ui.userfront.app_label]
admin_console = ""
baron = ""
dev_console = ""
[ui.userfront.auth_method]
ory = ""
session = ""
[ui.userfront.dashboard]
link_status_label = ""
last_auth_label = ""
status_history = ""
[ui.userfront.device]
android = ""
ios = ""
linux = ""
macos = ""
windows = ""
[ui.userfront.error]
go_home = ""
go_login = ""
switch_account = ""
[ui.userfront.forgot]
heading = ""
input_label = ""
submit = ""
title = ""
[ui.userfront.login]
forgot_password = ""
signup = ""
[ui.userfront.login_success]
later = ""
qr = ""
title = ""
[ui.userfront.consent]
accept = ""
requested_scopes = ""
title = ""
[ui.userfront.nav]
dashboard = ""
logout = ""
profile = ""
qr_scan = ""
[ui.userfront.profile]
department_empty = ""
manage = ""
user_fallback = ""
[ui.userfront.qr]
rescan = ""
result_success = ""
title = ""
[ui.userfront.reset]
confirm_password = ""
new_password = ""
submit = ""
subtitle = ""
title = ""
[ui.userfront.sections]
apps = ""
audit = ""
sessions = ""
[ui.userfront.session]
active = ""
unknown = ""
[ui.userfront.signup]
complete = ""
next_step = ""
title = ""
[msg.userfront]
greeting = ""
[msg.userfront.audit]
browser = ""
date = ""
device = ""
end = ""
filtered_empty = ""
ip = ""
load_more_error = ""
result = ""
session_id = ""
status = ""
[msg.userfront.consent]
accept_error = ""
client_id = ""
client_unknown = ""
description = ""
load_error = ""
missing_redirect = ""
redirect_notice = ""
scope_count = ""
[msg.userfront.consent.cancel]
confirm = ""
error = ""
[msg.userfront.consent.scope]
email = ""
offline_access = ""
openid = ""
phone = ""
profile = ""
[msg.userfront.dashboard]
approved_device = ""
approved_ip = ""
audit_empty = ""
audit_load_error = ""
auto_login_supported = ""
auth_method = ""
client_id = ""
client_id_missing = ""
current_status = ""
last_auth = ""
link_missing = ""
link_open_error = ""
render_error = ""
session_id_copied = ""
[msg.userfront.dashboard.activities]
empty = ""
empty_detail = ""
error = ""
[msg.userfront.dashboard.sessions]
browser = ""
empty = ""
empty_detail = ""
error = ""
os = ""
recent_app = ""
session_id = ""
[msg.userfront.dashboard.sessions.revoke]
confirm = ""
error = ""
success = ""
[msg.userfront.dashboard.approved_session]
copy_click = ""
copy_tap = ""
none = ""
[msg.userfront.dashboard.revoke]
confirm = ""
error = ""
success = ""
[msg.userfront.dashboard.scopes]
empty = ""
[msg.userfront.dashboard.timeline]
load_error = ""
[msg.userfront.error]
detail_contact = ""
detail_generic = ""
detail_request = ""
id = ""
title = ""
title_generic = ""
title_with_code = ""
type = ""
[msg.userfront.error.tenant]
account = ""
account_unknown = ""
affiliated_tenants = ""
allowed_box_title = ""
allowed_tenants = ""
detail = ""
load_failed = ""
loading = ""
lookup_fallback = ""
page_title = ""
primary_tenant = ""
tenant = ""
tenant_unknown = ""
title = ""
[msg.userfront.error.ory]
"$normalizedCode" = ""
access_denied = ""
consent_required = ""
interaction_required = ""
invalid_client = ""
invalid_grant = ""
invalid_request = ""
invalid_scope = ""
login_required = ""
request_forbidden = ""
server_error = ""
temporarily_unavailable = ""
unauthorized_client = ""
unsupported_response_type = ""
[msg.userfront.error.whitelist]
"$normalizedCode" = ""
bad_request = ""
invalid_session = ""
not_found = ""
password_or_email_mismatch = ""
rate_limited = ""
recovery_expired = ""
recovery_invalid = ""
settings_disabled = ""
verification_required = ""
[msg.userfront.forgot]
description = ""
dry_send = ""
error = ""
input_required = ""
sent = ""
[msg.userfront.login]
cookie_check_failed = ""
dry_send = ""
link_failed = ""
link_send_failed = ""
link_sent_email = ""
link_sent_phone = ""
link_timeout = ""
no_account = ""
oidc_failed = ""
qr_expired = ""
qr_init_failed = ""
qr_login_required = ""
token_missing = ""
verification_failed = ""
[msg.userfront.login.link]
approved = ""
helper = ""
missing_login_id = ""
missing_phone = ""
resend_wait = ""
short_code_help = ""
[msg.userfront.login.password]
failed = ""
missing_credentials = ""
[msg.userfront.login.qr]
load_failed = ""
scan_hint = ""
[msg.userfront.login.short_code]
invalid = ""
[msg.userfront.login.unregistered]
body = ""
[msg.userfront.login.verification]
approved = ""
approved_local = ""
approved_remote = ""
pending_remote = ""
close_hint = ""
success = ""
[msg.userfront.login_success]
subtitle = ""
[msg.userfront.profile]
department_missing = ""
department_required = ""
email_missing = ""
greeting = ""
load_failed = ""
name_missing = ""
name_required = ""
phone_required = ""
phone_verify_required = ""
update_failed = ""
update_success = ""
[msg.userfront.profile.password]
change_failed = ""
changed = ""
current_required = ""
mismatch = ""
new_required = ""
subtitle = ""
[msg.userfront.profile.phone]
code_sent = ""
send_failed = ""
verified = ""
verify_failed = ""
verify_notice = ""
[msg.userfront.profile.section]
basic = ""
organization = ""
security = ""
[msg.userfront.qr]
approve_error = ""
approve_success = ""
camera_error = ""
permission_error = ""
permission_required = ""
[msg.userfront.reset]
invalid_body = ""
invalid_link = ""
invalid_title = ""
policy_loading = ""
success = ""
[msg.userfront.reset.error]
empty_password = ""
generic = ""
lowercase = ""
min_length = ""
min_types = ""
mismatch = ""
number = ""
symbol = ""
uppercase = ""
[msg.userfront.reset.policy]
lowercase = ""
min_length = ""
min_types = ""
number = ""
symbol = ""
uppercase = ""
[msg.userfront.sections]
apps_subtitle = ""
audit_subtitle = ""
[msg.userfront.settings]
disabled = ""
[msg.userfront.signup]
failed = ""
privacy_full = ""
tos_full = ""
[msg.userfront.signup.agreement]
all_hint = ""
description = ""
privacy_summary = ""
progress = ""
title = ""
tos_summary = ""
[msg.userfront.signup.auth]
affiliate_notice = ""
title = ""
[msg.userfront.signup.email]
code_mismatch = ""
duplicate = ""
invalid = ""
send_failed = ""
verified = ""
verify_failed = ""
[msg.userfront.signup.password]
length_required = ""
lowercase_required = ""
mismatch = ""
number_required = ""
symbol_required = ""
title = ""
uppercase_required = ""
[msg.userfront.signup.password.rule]
lowercase = ""
min_length = ""
min_types = ""
number = ""
symbol = ""
uppercase = ""
[msg.userfront.signup.phone]
code_mismatch = ""
send_failed = ""
verified = ""
verify_failed = ""
[msg.userfront.signup.policy]
loading = ""
lowercase = ""
min_length = ""
min_types = ""
number = ""
summary = ""
symbol = ""
uppercase = ""
[msg.userfront.signup.profile]
affiliate_hint = ""
title = ""
[msg.userfront.signup.success]
body = ""
title = ""
[ui.common]
add = ""
admin_only = ""
all = ""
assign = ""
back = ""
back_to_login = ""
cancel = ""
change_file = ""
clear_search = ""
close = ""
collapse = ""
confirm = ""
copy = ""
create = ""
delete = ""
details = ""
edit = ""
enabled = ""
export = ""
fail = ""
generate = ""
go_home = ""
hyphen = ""
language = ""
language_en = ""
language_ko = ""
manage = ""
na = ""
never = ""
next = ""
none = ""
page_of = ""
prev = ""
previous = ""
qr = ""
read_only = ""
refresh = ""
remove = ""
resend = ""
reset = ""
retry = ""
save = ""
search = ""
select = ""
select_file = ""
select_placeholder = ""
show_more = ""
success = ""
theme_dark = ""
theme_light = ""
theme_toggle = ""
unknown = ""
view = ""
[ui.common.badge]
admin_only = ""
command_only = ""
system = ""
[ui.common.status]
active = ""
archived = ""
baron_guest = ""
blocked = ""
extended_leave = ""
failure = ""
inactive = ""
leave_of_absence = ""
ok = ""
pending = ""
preboarding = ""
success = ""
suspended = ""
temporary_leave = ""
[ui.userfront]
app_title = ""
[ui.userfront.app_label]
admin_console = ""
baron = ""
dev_console = ""
[ui.userfront.audit]
[ui.userfront.audit.table]
action = ""
app = ""
auth_method = ""
browser = ""
date = ""
device = ""
ip = ""
pending = ""
result = ""
session_id = ""
status = ""
[ui.userfront.auth_method]
ory = ""
session = ""
[ui.userfront.consent]
accept = ""
requested_scopes = ""
title = ""
[ui.userfront.consent.cancel]
confirm_button = ""
title = ""
[ui.userfront.dashboard]
last_auth_label = ""
status_history = ""
[ui.userfront.dashboard.activity]
linked = ""
[ui.userfront.dashboard.sessions]
active_badge = ""
current_badge = ""
current_disabled = ""
unknown_device = ""
unknown_session = ""
[ui.userfront.dashboard.sessions.revoke]
action = ""
title = ""
[ui.userfront.dashboard.approved_session]
default = ""
userfront = ""
[ui.userfront.dashboard.revoke]
confirm_button = ""
title = ""
[ui.userfront.dashboard.scopes]
title = ""
[ui.userfront.dashboard.status]
revoked = ""
[ui.userfront.device]
android = ""
ios = ""
linux = ""
macos = ""
windows = ""
[ui.userfront.error]
go_home = ""
go_login = ""
switch_account = ""
[ui.userfront.forgot]
heading = ""
input_label = ""
submit = ""
title = ""
[ui.userfront.login]
forgot_password = ""
signup = ""
[ui.userfront.login.action]
submit = ""
[ui.userfront.login.field]
login_id = ""
password = ""
[ui.userfront.login.link]
action_label = ""
code_only = ""
page_title = ""
resend_with_time = ""
send = ""
title = ""
[ui.userfront.login.qr]
expired = ""
refresh = ""
remaining = ""
[ui.userfront.login.short_code]
digits = ""
expire_time = ""
prefix = ""
submit = ""
[ui.userfront.login.tabs]
link = ""
password = ""
qr = ""
[ui.userfront.login.unregistered]
action = ""
title = ""
[ui.userfront.login.verification]
action_label = ""
action_label_remote = ""
action_label_close = ""
page_title = ""
title = ""
title_pending = ""
title_remote = ""
[ui.userfront.login_success]
later = ""
qr = ""
title = ""
[ui.userfront.nav]
dashboard = ""
logout = ""
profile = ""
qr_scan = ""
[ui.userfront.profile]
department_empty = ""
manage = ""
user_fallback = ""
[ui.userfront.profile.field]
affiliation = ""
company_code = ""
department = ""
email = ""
name = ""
tenant = ""
tenant_slug = ""
[ui.userfront.profile.password]
change = ""
confirm = ""
current = ""
forgot = ""
new = ""
title = ""
[ui.userfront.profile.phone]
code_hint = ""
request_code = ""
title = ""
[ui.userfront.profile.section]
basic = ""
organization = ""
security = ""
[ui.userfront.qr]
request_permission = ""
rescan = ""
result_failure = ""
result_success = ""
title = ""
[ui.userfront.reset]
confirm_password = ""
new_password = ""
submit = ""
subtitle = ""
title = ""
[ui.userfront.sections]
apps = ""
audit = ""
[ui.userfront.session]
active = ""
unknown = ""
[ui.userfront.signup]
complete = ""
next_step = ""
title = ""
[ui.userfront.signup.agreement]
all = ""
privacy_title = ""
required = ""
tos_title = ""
[ui.userfront.signup.auth]
code_label = ""
request_code = ""
[ui.userfront.signup.auth.email]
label = ""
title = ""
[ui.userfront.signup.password]
confirm_label = ""
label = ""
[ui.userfront.signup.phone]
label = ""
title = ""
[ui.userfront.signup.profile]
affiliation_type = ""
company = ""
department = ""
department_optional = ""
name = ""
[ui.userfront.signup.steps]
agreement = ""
password = ""
profile = ""
verify = ""
[ui.userfront.signup.success]
action = ""
[ui.userfront.audit.filter]
title = ""
toggle_label = ""
[msg.userfront.audit.filter]
description = ""

34
baron-sso/userfront/ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = kr.co.baroncs.userfront;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.co.baroncs.userfront.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.co.baroncs.userfront.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = kr.co.baroncs.userfront.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = kr.co.baroncs.userfront;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = kr.co.baroncs.userfront;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Frontend</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>userfront</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>This app requires camera access to scan QR codes for login.</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View File

@@ -0,0 +1,30 @@
const Map<String, String> internalErrorWhitelistMessageKeys = {
'settings_disabled': 'msg.userfront.error.whitelist.settings_disabled',
'invalid_session': 'msg.userfront.error.whitelist.invalid_session',
'verification_required':
'msg.userfront.error.whitelist.verification_required',
'recovery_expired': 'msg.userfront.error.whitelist.recovery_expired',
'recovery_invalid': 'msg.userfront.error.whitelist.recovery_invalid',
'rate_limited': 'msg.userfront.error.whitelist.rate_limited',
'not_found': 'msg.userfront.error.whitelist.not_found',
'bad_request': 'msg.userfront.error.whitelist.bad_request',
'password_or_email_mismatch':
'msg.userfront.error.whitelist.password_or_email_mismatch',
'tenant_not_allowed': 'msg.userfront.error.whitelist.tenant_not_allowed',
};
const Set<String> oryBypassErrorCodes = {
'access_denied',
'consent_required',
'interaction_required',
'invalid_client',
'invalid_grant',
'invalid_request',
'invalid_scope',
'login_required',
'request_forbidden',
'server_error',
'temporarily_unavailable',
'unauthorized_client',
'unsupported_response_type',
};

View File

@@ -0,0 +1,75 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart' hide tr;
import 'package:flutter/material.dart';
import 'package:userfront/i18n.dart';
import '../services/web_window.dart';
import 'locale_storage.dart';
import 'locale_utils.dart';
class LocaleGate extends StatefulWidget {
const LocaleGate({super.key, required this.localeCode, required this.child});
final String localeCode;
final Widget child;
@override
State<LocaleGate> createState() => _LocaleGateState();
}
class _LocaleGateState extends State<LocaleGate> {
bool _syncScheduled = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_scheduleLocaleSync();
}
@override
void didUpdateWidget(LocaleGate oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.localeCode != widget.localeCode) {
_scheduleLocaleSync();
}
}
void _scheduleLocaleSync() {
if (_syncScheduled) {
return;
}
_syncScheduled = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_syncScheduled = false;
if (!mounted) {
return;
}
unawaited(_applyLocale());
});
}
Future<void> _applyLocale() async {
if (!mounted) {
return;
}
final normalized = normalizeLocaleCode(widget.localeCode);
LocaleStorage.write(normalized);
final localization = EasyLocalization.of(context);
if (localization == null) {
return;
}
if (localization.currentLocale?.languageCode == normalized) {
webWindow.setTitle(tr('ui.userfront.app_title'));
return;
}
await localization.setLocale(Locale(normalized));
if (!mounted) {
return;
}
webWindow.setTitle(tr('ui.userfront.app_title'));
}
@override
Widget build(BuildContext context) => widget.child;
}

View File

@@ -0,0 +1,115 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
const _translationAssetPrefix = 'assets/translations/';
const _templateFileName = 'template.toml';
const _safeFallbackLocaleCode = 'en';
List<String> extractSupportedLocaleCodesFromAssets(Iterable<String> assets) {
final localeCodes = <String>{};
for (final asset in assets) {
if (!asset.startsWith(_translationAssetPrefix) ||
!asset.endsWith('.toml')) {
continue;
}
final fileName = asset.substring(_translationAssetPrefix.length);
if (fileName.contains('/') || fileName == _templateFileName) {
continue;
}
final rawCode = fileName.substring(0, fileName.length - '.toml'.length);
final normalized = rawCode.toLowerCase().replaceAll('_', '-');
if (_isValidLocaleCode(normalized)) {
localeCodes.add(normalized);
}
}
final sorted = localeCodes.toList()..sort();
return sorted;
}
class LocaleRegistry {
static final Set<String> _localeCodes = <String>{};
static bool _initialized = false;
static void primeWithDefaults({
Iterable<String> localeCodes = const ['en', 'ko'],
}) {
if (_localeCodes.isNotEmpty) {
return;
}
_localeCodes.addAll(
localeCodes
.map((code) => code.toLowerCase().replaceAll('_', '-'))
.where(_isValidLocaleCode),
);
if (_localeCodes.isEmpty) {
_localeCodes.add(_safeFallbackLocaleCode);
}
}
static Future<void> initialize({AssetBundle? assetBundle}) async {
if (_initialized) {
return;
}
final bundle = assetBundle ?? rootBundle;
try {
final manifest = await AssetManifest.loadFromAssetBundle(bundle);
final extracted = extractSupportedLocaleCodesFromAssets(
manifest.listAssets(),
);
_localeCodes.addAll(extracted);
} catch (_) {
// manifest 로딩 실패 시 안전 fallback으로 계속 진행합니다.
}
if (_localeCodes.isEmpty) {
_localeCodes.add(_safeFallbackLocaleCode);
}
_initialized = true;
}
static List<String> get supportedLocaleCodes {
final sorted = _localeCodes.toList()..sort();
return List.unmodifiable(sorted);
}
static String get fallbackLocaleCode {
final supported = supportedLocaleCodes;
if (supported.isEmpty) {
return _safeFallbackLocaleCode;
}
if (supported.contains('en')) {
return 'en';
}
return supported.first;
}
static bool contains(String code) {
return _localeCodes.contains(code.toLowerCase());
}
@visibleForTesting
static void setSupportedLocaleCodesForTest(Iterable<String> localeCodes) {
_localeCodes
..clear()
..addAll(
localeCodes
.map((code) => code.toLowerCase().replaceAll('_', '-'))
.where(_isValidLocaleCode),
);
if (_localeCodes.isEmpty) {
_localeCodes.add(_safeFallbackLocaleCode);
}
_initialized = true;
}
@visibleForTesting
static void resetForTest() {
_localeCodes.clear();
_initialized = false;
}
}
bool _isValidLocaleCode(String value) {
return RegExp(r'^[a-z]{2,3}$').hasMatch(value);
}

View File

@@ -0,0 +1,59 @@
import 'package:flutter/foundation.dart';
import 'locale_storage_backend.dart';
import 'locale_storage_stub.dart'
if (dart.library.js_interop) 'locale_storage_web.dart';
abstract class LocaleStorage {
static bool _forceMemory = false;
static bool _forceSession = false;
static void _syncTestMode() {
if (_forceMemory) {
localeStorage.setTestMode(LocaleStorageTestMode.memoryOnly);
return;
}
if (_forceSession) {
localeStorage.setTestMode(LocaleStorageTestMode.sessionOnly);
return;
}
localeStorage.setTestMode(LocaleStorageTestMode.normal);
}
static String? read() => localeStorage.read();
static void write(String locale) => localeStorage.write(locale);
@visibleForTesting
static void setTestModeForTests(LocaleStorageTestMode mode) {
_forceMemory = mode == LocaleStorageTestMode.memoryOnly;
_forceSession = mode == LocaleStorageTestMode.sessionOnly;
_syncTestMode();
}
@visibleForTesting
static void clearForTests() {
localeStorage.clearForTests();
_forceMemory = false;
_forceSession = false;
}
@visibleForTesting
static void seedLegacyForTests(String locale) {
localeStorage.seedLegacyForTests(locale);
}
@visibleForTesting
static LocaleStorageDebugState debugStateForTests() {
return localeStorage.debugStateForTests();
}
static void forceMemoryStorageForTests(bool value) {
_forceMemory = value;
_syncTestMode();
}
static void forceSessionStorageForTests(bool value) {
_forceSession = value;
_syncTestMode();
}
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/foundation.dart';
enum LocaleStorageTestMode { normal, sessionOnly, memoryOnly }
@immutable
class LocaleStorageDebugState {
const LocaleStorageDebugState({
required this.mode,
this.localCurrent,
this.localLegacy,
this.sessionCurrent,
this.sessionLegacy,
this.memoryCurrent,
this.memoryLegacy,
});
final LocaleStorageTestMode mode;
final String? localCurrent;
final String? localLegacy;
final String? sessionCurrent;
final String? sessionLegacy;
final String? memoryCurrent;
final String? memoryLegacy;
}
abstract interface class LocaleStorageBackend {
String? read();
void write(String locale);
void setTestMode(LocaleStorageTestMode mode);
void clearForTests();
void seedLegacyForTests(String locale);
LocaleStorageDebugState debugStateForTests();
}

View File

@@ -0,0 +1,245 @@
import 'locale_storage_backend.dart';
import 'locale_storage_policy.dart';
enum _StorageTarget { local, session, memory }
abstract interface class LocaleStorageTarget {
String? read(String key);
bool write(String key, String value);
bool remove(String key);
void clear();
}
class LocaleStorageNoopTarget implements LocaleStorageTarget {
const LocaleStorageNoopTarget();
@override
String? read(String key) => null;
@override
bool write(String key, String value) => false;
@override
bool remove(String key) => false;
@override
void clear() {}
}
class LocaleStorageCallbackTarget implements LocaleStorageTarget {
LocaleStorageCallbackTarget({
required this.readCallback,
required this.writeCallback,
required this.removeCallback,
required this.clearCallback,
});
final String? Function(String key) readCallback;
final void Function(String key, String value) writeCallback;
final void Function(String key) removeCallback;
final void Function() clearCallback;
@override
String? read(String key) => readCallback(key);
@override
bool write(String key, String value) {
writeCallback(key, value);
return true;
}
@override
bool remove(String key) {
removeCallback(key);
return true;
}
@override
void clear() => clearCallback();
}
class LocaleStorageEngine implements LocaleStorageBackend {
LocaleStorageEngine({
required LocaleStorageTarget localTarget,
required LocaleStorageTarget sessionTarget,
}) : _localTarget = localTarget,
_sessionTarget = sessionTarget;
final LocaleStorageTarget _localTarget;
final LocaleStorageTarget _sessionTarget;
final Map<String, String> _memory = {};
LocaleStorageTestMode _mode = LocaleStorageTestMode.normal;
List<_StorageTarget> _fallbackTargets() {
switch (_mode) {
case LocaleStorageTestMode.normal:
return [
_StorageTarget.local,
_StorageTarget.session,
_StorageTarget.memory,
];
case LocaleStorageTestMode.sessionOnly:
return [_StorageTarget.session, _StorageTarget.memory];
case LocaleStorageTestMode.memoryOnly:
return [_StorageTarget.memory];
}
}
String? _safeReadTarget(_StorageTarget target, String key) {
try {
switch (target) {
case _StorageTarget.local:
return _localTarget.read(key);
case _StorageTarget.session:
return _sessionTarget.read(key);
case _StorageTarget.memory:
return _memory[key];
}
} catch (_) {
return null;
}
}
bool _safeWriteTarget(_StorageTarget target, String key, String value) {
try {
switch (target) {
case _StorageTarget.local:
return _localTarget.write(key, value);
case _StorageTarget.session:
return _sessionTarget.write(key, value);
case _StorageTarget.memory:
_memory[key] = value;
return true;
}
} catch (_) {
return false;
}
}
bool _safeRemoveTarget(_StorageTarget target, String key) {
try {
switch (target) {
case _StorageTarget.local:
return _localTarget.remove(key);
case _StorageTarget.session:
return _sessionTarget.remove(key);
case _StorageTarget.memory:
_memory.remove(key);
return true;
}
} catch (_) {
return false;
}
}
void _safeClearTarget(_StorageTarget target) {
try {
switch (target) {
case _StorageTarget.local:
_localTarget.clear();
case _StorageTarget.session:
_sessionTarget.clear();
case _StorageTarget.memory:
_memory.clear();
}
} catch (_) {
// 테스트 정리 단계에서는 clear 예외를 무시합니다.
}
}
String? _readByKey(String key) {
for (final target in _fallbackTargets()) {
final value = _safeReadTarget(target, key);
if (value != null) {
return value;
}
}
return null;
}
void _writeByKey(String key, String value) {
for (final target in _fallbackTargets()) {
if (_safeWriteTarget(target, key, value)) {
return;
}
}
}
void _removeEverywhere(String key) {
_safeRemoveTarget(_StorageTarget.local, key);
_safeRemoveTarget(_StorageTarget.session, key);
_memory.remove(key);
}
@override
String? read() {
final current = _readByKey(LocaleStoragePolicy.currentKey);
if (LocaleStoragePolicy.hasValue(current)) {
return current;
}
final legacy = _readByKey(LocaleStoragePolicy.legacyKey);
if (LocaleStoragePolicy.shouldMigrateLegacy(
current: current,
legacy: legacy,
) &&
legacy != null) {
_writeByKey(LocaleStoragePolicy.currentKey, legacy);
_removeEverywhere(LocaleStoragePolicy.legacyKey);
return legacy;
}
return null;
}
@override
void write(String locale) {
_writeByKey(LocaleStoragePolicy.currentKey, locale);
}
@override
void setTestMode(LocaleStorageTestMode mode) {
_mode = mode;
}
@override
void clearForTests() {
_safeClearTarget(_StorageTarget.local);
_safeClearTarget(_StorageTarget.session);
_memory.clear();
_mode = LocaleStorageTestMode.normal;
}
@override
void seedLegacyForTests(String locale) {
_writeByKey(LocaleStoragePolicy.legacyKey, locale);
}
@override
LocaleStorageDebugState debugStateForTests() {
return LocaleStorageDebugState(
mode: _mode,
localCurrent: _safeReadTarget(
_StorageTarget.local,
LocaleStoragePolicy.currentKey,
),
localLegacy: _safeReadTarget(
_StorageTarget.local,
LocaleStoragePolicy.legacyKey,
),
sessionCurrent: _safeReadTarget(
_StorageTarget.session,
LocaleStoragePolicy.currentKey,
),
sessionLegacy: _safeReadTarget(
_StorageTarget.session,
LocaleStoragePolicy.legacyKey,
),
memoryCurrent: _memory[LocaleStoragePolicy.currentKey],
memoryLegacy: _memory[LocaleStoragePolicy.legacyKey],
);
}
}

View File

@@ -0,0 +1,13 @@
class LocaleStoragePolicy {
static const currentKey = 'locale';
static const legacyKey = 'baron_locale';
static bool hasValue(String? value) => value != null && value.isNotEmpty;
static bool shouldMigrateLegacy({
required String? current,
required String? legacy,
}) {
return !hasValue(current) && hasValue(legacy);
}
}

View File

@@ -0,0 +1,7 @@
import 'locale_storage_backend.dart';
import 'locale_storage_engine.dart';
final LocaleStorageBackend localeStorage = LocaleStorageEngine(
localTarget: const LocaleStorageNoopTarget(),
sessionTarget: const LocaleStorageNoopTarget(),
);

View File

@@ -0,0 +1,22 @@
// ignore_for_file: avoid_web_libraries_in_flutter
import 'package:web/web.dart' as web;
import 'locale_storage_backend.dart';
import 'locale_storage_engine.dart';
final LocaleStorageBackend localeStorage = LocaleStorageEngine(
localTarget: LocaleStorageCallbackTarget(
readCallback: (key) => web.window.localStorage.getItem(key),
writeCallback: (key, value) => web.window.localStorage.setItem(key, value),
removeCallback: (key) => web.window.localStorage.removeItem(key),
clearCallback: () => web.window.localStorage.clear(),
),
sessionTarget: LocaleStorageCallbackTarget(
readCallback: (key) => web.window.sessionStorage.getItem(key),
writeCallback: (key, value) =>
web.window.sessionStorage.setItem(key, value),
removeCallback: (key) => web.window.sessionStorage.removeItem(key),
clearCallback: () => web.window.sessionStorage.clear(),
),
);

View File

@@ -0,0 +1,117 @@
import 'dart:ui';
import 'locale_storage.dart';
import 'locale_registry.dart';
String get defaultLocaleCode => LocaleRegistry.fallbackLocaleCode;
String normalizeLocaleCode(String? code) {
final supportedLocaleCodes = LocaleRegistry.supportedLocaleCodes;
final fallbackLocaleCode = LocaleRegistry.fallbackLocaleCode;
if (code == null || code.isEmpty) {
return fallbackLocaleCode;
}
final normalized = code.toLowerCase().replaceAll('_', '-');
if (supportedLocaleCodes.contains(normalized)) {
return normalized;
}
final languageCode = normalized.split('-').first;
if (supportedLocaleCodes.contains(languageCode)) {
return languageCode;
}
return fallbackLocaleCode;
}
String resolvePreferredLocaleCode() {
final stored = LocaleStorage.read();
if (stored != null && stored.isNotEmpty) {
final normalizedStored = normalizeLocaleCode(stored);
if (LocaleRegistry.contains(normalizedStored)) {
return normalizedStored;
}
}
final deviceLocale = PlatformDispatcher.instance.locale;
final countryCode = deviceLocale.countryCode;
final languageTag = countryCode == null || countryCode.isEmpty
? deviceLocale.languageCode
: '${deviceLocale.languageCode}-$countryCode';
return normalizeLocaleCode(languageTag);
}
String? extractLocaleFromPath(Uri uri) {
final supportedLocaleCodes = LocaleRegistry.supportedLocaleCodes;
if (uri.pathSegments.isEmpty) {
return null;
}
final code = uri.pathSegments.first.toLowerCase();
if (supportedLocaleCodes.contains(code)) {
return code;
}
return null;
}
String stripLocalePath(Uri uri) {
final supportedLocaleCodes = LocaleRegistry.supportedLocaleCodes;
final segments = uri.pathSegments;
if (segments.isNotEmpty &&
supportedLocaleCodes.contains(segments.first.toLowerCase())) {
final rest = segments.skip(1).join('/');
if (rest.isEmpty) {
return '/';
}
return '/$rest';
}
return uri.path;
}
String buildLocalizedPath(String localeCode, Uri uri) {
final supportedLocaleCodes = LocaleRegistry.supportedLocaleCodes;
final segments = uri.pathSegments;
Iterable<String> restSegments = segments;
if (segments.isNotEmpty) {
final head = segments.first.toLowerCase();
if (supportedLocaleCodes.contains(head)) {
restSegments = segments.skip(1);
}
}
final newPath = '/${[localeCode, ...restSegments].join('/')}';
// Return only the path and query part to avoid GoRouter confusion with full URLs
final newUri = uri.replace(path: newPath);
String result = newUri.path;
if (newUri.hasQuery) {
result += '?${newUri.query}';
}
if (newUri.hasFragment) {
result += '#${newUri.fragment}';
}
return result;
}
String buildSigninRedirectPath(String localeCode, Uri uri) {
final newPath = '/$localeCode/signin';
final newUri = uri.replace(path: newPath);
String result = newUri.path;
if (newUri.hasQuery) {
result += '?${newUri.query}';
}
if (newUri.hasFragment) {
result += '#${newUri.fragment}';
}
return result;
}
String buildLocalizedHomePath(Uri uri, {String? preferredLocaleCode}) {
final resolvedLocale =
extractLocaleFromPath(uri) ??
normalizeLocaleCode(preferredLocaleCode ?? resolvePreferredLocaleCode());
return '/$resolvedLocale/dashboard';
}
String buildLocalizedSigninPath(Uri uri, {String? preferredLocaleCode}) {
final resolvedLocale =
extractLocaleFromPath(uri) ??
normalizeLocaleCode(preferredLocaleCode ?? resolvePreferredLocaleCode());
return '/$resolvedLocale/signin';
}

View File

@@ -0,0 +1,54 @@
import 'dart:ui';
import 'package:easy_localization/easy_localization.dart';
import '../../i18n_data.dart';
class TomlAssetLoader extends AssetLoader {
const TomlAssetLoader();
@override
Future<Map<String, dynamic>> load(String path, Locale locale) async {
final languageCode = locale.languageCode.toLowerCase();
return switch (languageCode) {
'ko' => _normalizedKoStrings,
'en' => _normalizedEnStrings,
_ => _normalizedEnStrings,
};
}
}
final Map<String, dynamic> _normalizedKoStrings = _normalizeFlatTranslations(
koStrings,
);
final Map<String, dynamic> _normalizedEnStrings = _normalizeFlatTranslations(
enStrings,
);
Map<String, dynamic> _normalizeFlatTranslations(Map<String, String> flatMap) =>
Map.fromEntries(
flatMap.entries
.where((entry) => _isUserfrontTranslationKey(entry.key))
.map(
(entry) =>
MapEntry(entry.key, _normalizeLocalizationValue(entry.value)),
),
);
bool _isUserfrontTranslationKey(String key) {
return key.startsWith('domain.') ||
key.startsWith('err.userfront.') ||
key.startsWith('msg.userfront.') ||
key.startsWith('ui.userfront.') ||
key.startsWith('ui.common.');
}
String _normalizeLocalizationValue(String value) {
return value
.replaceAllMapped(
RegExp(r'\{\{\s*([a-zA-Z0-9_]+)\s*\}\}'),
(match) => '{${match.group(1)}}',
)
.replaceAll(r'\\n', '\n')
.replaceAll(r'\n', '\n');
}

View File

@@ -0,0 +1,16 @@
import 'package:flutter/foundation.dart';
import '../services/auth_token_store.dart';
class AuthNotifier extends ChangeNotifier {
static final AuthNotifier instance = AuthNotifier();
Future<void> onLoginSuccess(String token, {String? provider}) async {
AuthTokenStore.setToken(token, provider: provider);
AuthTokenStore.clearPendingProvider();
notifyListeners();
}
void notify() {
notifyListeners();
}
}

View File

@@ -0,0 +1,40 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'runtime_env.dart';
class AuditService {
static String get _baseUrl => runtimeBackendUrl();
static Future<void> logEvent({
required String userId,
required String eventType,
required String status,
String? details,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/audit');
try {
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'user_id': userId,
'event_type': eventType,
'status': status,
'details': details,
}),
);
if (response.statusCode >= 200 && response.statusCode < 300) {
debugPrint('Audit log sent successfully');
} else {
debugPrint(
'Failed to send audit log: ${response.statusCode} ${response.body}',
);
}
} catch (e) {
debugPrint('Error sending audit log: $e');
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
import 'auth_token_store_stub.dart'
if (dart.library.js_interop) 'auth_token_store_web.dart';
class AuthTokenStore {
static bool hasToken() {
final token = getToken();
return token != null && token.isNotEmpty;
}
static String? getToken() => authTokenStore.getToken();
static String? getProvider() => authTokenStore.getProvider();
static bool usesCookie() => authTokenStore.usesCookie();
static void setToken(String token, {String? provider}) {
authTokenStore.setToken(token, provider: provider);
}
static void setCookieMode({String? provider}) {
authTokenStore.setCookieMode(provider: provider);
}
static String? getPendingProvider() => authTokenStore.getPendingProvider();
static void setPendingProvider(String? provider) {
authTokenStore.setPendingProvider(provider);
}
static void clearPendingProvider() {
authTokenStore.setPendingProvider(null);
}
static void skipNextCookieSessionCheck() {
authTokenStore.skipNextCookieSessionCheck();
}
static bool consumeSkipCookieSessionCheck() {
return authTokenStore.consumeSkipCookieSessionCheck();
}
static void clear() {
authTokenStore.clear();
}
}

View File

@@ -0,0 +1,124 @@
abstract class AuthTokenStorageTarget {
String? read(String key);
void write(String key, String value);
void remove(String key);
}
class AuthTokenStoreBackend {
AuthTokenStoreBackend({
required AuthTokenStorageTarget localTarget,
required AuthTokenStorageTarget sessionTarget,
}) : _targets = [localTarget, sessionTarget, _MemoryStorageTarget()];
static const _tokenKey = 'baron_auth_token';
static const _providerKey = 'baron_auth_provider';
static const _cookieModeKey = 'baron_auth_cookie_mode';
static const _pendingProviderKey = 'baron_auth_pending_provider';
static const _skipCookieSessionCheckKey =
'baron_auth_skip_cookie_session_check';
final List<AuthTokenStorageTarget> _targets;
String? getToken() => _readFirst(_tokenKey);
String? getProvider() => _readFirst(_providerKey);
bool usesCookie() => _readFirst(_cookieModeKey) == '1';
void setToken(String token, {String? provider}) {
_writeAll(_tokenKey, token);
_removeAll(_cookieModeKey);
if (provider != null) {
_writeAll(_providerKey, provider);
}
}
void setCookieMode({String? provider}) {
_writeAll(_cookieModeKey, '1');
_removeAll(_tokenKey);
if (provider != null) {
_writeAll(_providerKey, provider);
}
}
String? getPendingProvider() => _readFirst(_pendingProviderKey);
bool consumeSkipCookieSessionCheck() {
final shouldSkip = _readFirst(_skipCookieSessionCheckKey) == '1';
if (shouldSkip) {
_removeAll(_skipCookieSessionCheckKey);
}
return shouldSkip;
}
void setPendingProvider(String? provider) {
if (provider == null || provider.isEmpty) {
_removeAll(_pendingProviderKey);
return;
}
_writeAll(_pendingProviderKey, provider);
}
void clear() {
_removeAll(_tokenKey);
_removeAll(_providerKey);
_removeAll(_cookieModeKey);
_removeAll(_pendingProviderKey);
_removeAll(_skipCookieSessionCheckKey);
}
void skipNextCookieSessionCheck() {
_writeAll(_skipCookieSessionCheckKey, '1');
}
String? _readFirst(String key) {
for (final target in _targets) {
try {
final value = target.read(key);
if (value != null && value.isNotEmpty) {
return value;
}
} catch (_) {
continue;
}
}
return null;
}
void _writeAll(String key, String value) {
for (final target in _targets) {
try {
target.write(key, value);
} catch (_) {
continue;
}
}
}
void _removeAll(String key) {
for (final target in _targets) {
try {
target.remove(key);
} catch (_) {
continue;
}
}
}
}
class _MemoryStorageTarget implements AuthTokenStorageTarget {
final Map<String, String> _memory = {};
@override
String? read(String key) => _memory[key];
@override
void remove(String key) {
_memory.remove(key);
}
@override
void write(String key, String value) {
_memory[key] = value;
}
}

View File

@@ -0,0 +1,53 @@
class AuthTokenStore {
String? _token;
String? _provider;
bool _cookieMode = false;
String? _pendingProvider;
bool _skipCookieSessionCheck = false;
String? getToken() => _token;
String? getProvider() => _provider;
bool usesCookie() => _cookieMode;
void setToken(String token, {String? provider}) {
_token = token;
_cookieMode = false;
_provider = provider;
}
void setCookieMode({String? provider}) {
_cookieMode = true;
_token = null;
if (provider != null) {
_provider = provider;
}
}
String? getPendingProvider() => _pendingProvider;
bool consumeSkipCookieSessionCheck() {
final shouldSkip = _skipCookieSessionCheck;
_skipCookieSessionCheck = false;
return shouldSkip;
}
void setPendingProvider(String? provider) {
_pendingProvider = provider;
}
void skipNextCookieSessionCheck() {
_skipCookieSessionCheck = true;
}
void clear() {
_token = null;
_provider = null;
_cookieMode = false;
_pendingProvider = null;
_skipCookieSessionCheck = false;
}
}
final authTokenStore = AuthTokenStore();

View File

@@ -0,0 +1,48 @@
// ignore_for_file: avoid_web_libraries_in_flutter
import 'dart:js_interop';
import 'auth_token_store_backend.dart';
@JS('window.localStorage')
external _JSStorage get _localStorage;
@JS('window.sessionStorage')
external _JSStorage get _sessionStorage;
@JS()
extension type _JSStorage(JSObject _) implements JSObject {
external String? getItem(String key);
external void setItem(String key, String value);
external void removeItem(String key);
}
class AuthTokenStore extends AuthTokenStoreBackend {
AuthTokenStore()
: super(
localTarget: _JsStorageTarget(_localStorage),
sessionTarget: _JsStorageTarget(_sessionStorage),
);
}
class _JsStorageTarget implements AuthTokenStorageTarget {
_JsStorageTarget(this._storage);
final _JSStorage _storage;
@override
String? read(String key) {
return _storage.getItem(key);
}
@override
void remove(String key) {
_storage.removeItem(key);
}
@override
void write(String key, String value) {
_storage.setItem(key, value);
}
}
final authTokenStore = AuthTokenStore();

View File

@@ -0,0 +1,6 @@
import 'package:http/http.dart' as http;
import 'http_client_stub.dart' if (dart.library.html) 'http_client_web.dart';
http.Client createHttpClient({bool withCredentials = false}) {
return httpClientFactory.create(withCredentials: withCredentials);
}

View File

@@ -0,0 +1,9 @@
import 'package:http/http.dart' as http;
class HttpClientFactory {
http.Client create({bool withCredentials = false}) {
return http.Client();
}
}
final httpClientFactory = HttpClientFactory();

View File

@@ -0,0 +1,12 @@
import 'package:http/browser_client.dart';
import 'package:http/http.dart' as http;
class HttpClientFactory {
http.Client create({bool withCredentials = false}) {
final client = BrowserClient();
client.withCredentials = withCredentials;
return client;
}
}
final httpClientFactory = HttpClientFactory();

View File

@@ -0,0 +1,139 @@
class LogPolicy {
static const Set<String> _sensitiveKeys = {
'password',
'currentpassword',
'newpassword',
'oldpassword',
'token',
'accesstoken',
'refreshtoken',
'secret',
'clientsecret',
'authorization',
'cookie',
'setcookie',
'verificationcode',
'code',
'loginchallenge',
'loginverifier',
'sessionjwt',
'accessjwt',
'refreshjwt',
};
static bool isProductionEnv(String? appEnv) {
final env = (appEnv ?? '').trim().toLowerCase();
return env == 'prod' ||
env == 'production' ||
env == 'stage' ||
env == 'staging';
}
static ({bool enabled, bool specified}) parseOptionalBoolFlag(String? raw) {
final value = (raw ?? '').trim().toLowerCase();
if (value == '1' ||
value == 'true' ||
value == 'yes' ||
value == 'y' ||
value == 'on') {
return (enabled: true, specified: true);
}
if (value == '0' ||
value == 'false' ||
value == 'no' ||
value == 'n' ||
value == 'off') {
return (enabled: false, specified: true);
}
return (enabled: false, specified: false);
}
static bool debugEnabled({
required String? appEnv,
required String? productionDebugFlag,
}) {
if (!isProductionEnv(appEnv)) {
return true;
}
final flag = parseOptionalBoolFlag(productionDebugFlag);
return flag.specified && flag.enabled;
}
static bool shouldRelayClientLog({
required String level,
required String? appEnv,
required String? productionDebugFlag,
}) {
final flag = parseOptionalBoolFlag(productionDebugFlag);
final debugRelayEnabled = isProductionEnv(appEnv)
? flag.specified && flag.enabled
: !(flag.specified && !flag.enabled);
if (debugRelayEnabled) {
return true;
}
final normalized = level.trim().toUpperCase();
return normalized == 'SEVERE' ||
normalized == 'ERROR' ||
normalized == 'WARNING' ||
normalized == 'WARN';
}
static String sanitizeMessage(String message) {
if (message.trim().isEmpty) {
return message;
}
var sanitized = message.replaceAllMapped(
RegExp(
r'"(password|currentpassword|newpassword|oldpassword|token|accesstoken|refreshtoken|secret|clientsecret|authorization|cookie|setcookie|verificationcode|code|loginchallenge|loginverifier|sessionjwt|accessjwt|refreshjwt)"\s*:\s*"[^"]*"',
caseSensitive: false,
),
(match) {
final key = match.group(1) ?? 'sensitive';
return '"$key":"*****"';
},
);
sanitized = sanitized.replaceAllMapped(
RegExp(
r'\b(password|current_password|currentpassword|new_password|newpassword|old_password|oldpassword|token|access_token|accesstoken|refresh_token|refreshtoken|authorization|cookie|session_jwt|sessionjwt|access_jwt|accessjwt|refresh_jwt|refreshjwt)\b\s*[:=]\s*([^\s,;]+)',
caseSensitive: false,
),
(match) {
final key = match.group(1) ?? 'sensitive';
return '$key=*****';
},
);
return sanitized;
}
static Map<String, dynamic> sanitizeData(Map<String, dynamic> input) {
final output = <String, dynamic>{};
for (final entry in input.entries) {
if (_isSensitiveKey(entry.key)) {
output[entry.key] = '*****';
} else {
output[entry.key] = _sanitizeValue(entry.value);
}
}
return output;
}
static dynamic _sanitizeValue(dynamic value) {
if (value is Map<String, dynamic>) {
return sanitizeData(value);
}
if (value is List) {
return value.map(_sanitizeValue).toList(growable: false);
}
if (value is String) {
return sanitizeMessage(value);
}
return value;
}
static bool _isSensitiveKey(String key) {
var normalized = key.trim().toLowerCase();
normalized = normalized.replaceAll(RegExp(r'[-_.\s]'), '');
return _sensitiveKeys.contains(normalized);
}
}

View File

@@ -0,0 +1,111 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart' as std_log;
import 'package:logger/logger.dart' as pretty_log;
import 'auth_proxy_service.dart';
import 'log_policy.dart';
import 'runtime_env.dart';
/// Global Logger Service for Baron SSO Frontend
class LoggerService {
static final LoggerService _instance = LoggerService._internal();
factory LoggerService() => _instance;
late final pretty_log.Logger _prettyLogger;
late final String _appEnv;
late final String _productionDebugFlag;
LoggerService._internal() {
_appEnv = envOrDefault('APP_ENV', 'dev');
_productionDebugFlag = envOrDefault(
'CLIENT_LOG_DEBUG',
envOrDefault('USERFRONT_DEBUG_LOG', ''),
);
final debugEnabled = LogPolicy.debugEnabled(
appEnv: _appEnv,
productionDebugFlag: _productionDebugFlag,
);
// 1. Initialize Pretty Logger for Dev
_prettyLogger = pretty_log.Logger(
printer: pretty_log.PrettyPrinter(
methodCount: 0,
errorMethodCount: 8,
lineLength: 120,
colors: true,
printEmojis: true,
dateTimeFormat: pretty_log.DateTimeFormat.onlyTimeAndSinceStart,
),
);
// 2. Configure Standard Logger (logging package)
std_log.Logger.root.level = debugEnabled
? std_log.Level.ALL
: std_log.Level.WARNING;
std_log.Logger.root.onRecord.listen((record) {
if (kReleaseMode) {
// [Production] Log as JSON
_logJson(record);
} else {
// [Development] Log using Pretty Printer
_logPretty(record);
}
});
}
/// Initialize the logger. Call this in main.dart
static void init() {
// Accessing the instance triggers the constructor
LoggerService();
std_log.Logger('BaronSSO').info('Logger initialized');
}
void _logPretty(std_log.LogRecord record) {
if (record.level >= std_log.Level.SEVERE) {
_prettyLogger.e(
record.message,
error: record.error,
stackTrace: record.stackTrace,
);
} else if (record.level >= std_log.Level.WARNING) {
_prettyLogger.w(record.message);
} else if (record.level >= std_log.Level.INFO) {
_prettyLogger.i(record.message);
} else {
_prettyLogger.d(record.message);
}
}
void _logJson(std_log.LogRecord record) {
final sanitizedMessage = LogPolicy.sanitizeMessage(record.message);
final logData = {
'time': record.time.toUtc().toIso8601String(), // Use UTC for consistency
'level': record.level.name,
'msg': sanitizedMessage,
'svc': 'baron-userfront',
if (record.error != null) 'error': record.error.toString(),
if (record.stackTrace != null) 'stack': record.stackTrace.toString(),
};
// 1. Print to Browser Console (F12)
debugPrint(jsonEncode(logData));
// 2. Relay to Backend (Docker Terminal)
if (LogPolicy.shouldRelayClientLog(
level: record.level.name,
appEnv: _appEnv,
productionDebugFlag: _productionDebugFlag,
)) {
AuthProxyService.sendLog(
record.level.name,
sanitizedMessage,
data: {
'client_time': record.time.toUtc().toIso8601String(),
'logger': record.loggerName,
if (record.error != null) 'error': record.error.toString(),
},
);
}
}
}

View File

@@ -0,0 +1,4 @@
import 'login_challenge_loop_guard_stub.dart'
if (dart.library.js_interop) 'login_challenge_loop_guard_web.dart';
final loginChallengeLoopGuard = createLoginChallengeLoopGuard();

View File

@@ -0,0 +1,5 @@
abstract class LoginChallengeLoopGuard {
bool shouldAllowAutoAccept(String loginChallenge, {int cooldownMs = 15000});
void markAutoAcceptAttempt(String loginChallenge);
void clear(String loginChallenge);
}

View File

@@ -0,0 +1,37 @@
import 'login_challenge_loop_guard_base.dart';
class _InMemoryLoginChallengeLoopGuard implements LoginChallengeLoopGuard {
final Map<String, int> _lastAttemptAtMs = <String, int>{};
@override
bool shouldAllowAutoAccept(String loginChallenge, {int cooldownMs = 15000}) {
final challenge = loginChallenge.trim();
if (challenge.isEmpty) {
return false;
}
final nowMs = DateTime.now().millisecondsSinceEpoch;
final lastMs = _lastAttemptAtMs[challenge];
if (lastMs == null) {
return true;
}
return nowMs - lastMs > cooldownMs;
}
@override
void markAutoAcceptAttempt(String loginChallenge) {
final challenge = loginChallenge.trim();
if (challenge.isEmpty) {
return;
}
_lastAttemptAtMs[challenge] = DateTime.now().millisecondsSinceEpoch;
}
@override
void clear(String loginChallenge) {
_lastAttemptAtMs.remove(loginChallenge.trim());
}
}
LoginChallengeLoopGuard createLoginChallengeLoopGuard() {
return _InMemoryLoginChallengeLoopGuard();
}

View File

@@ -0,0 +1,69 @@
// ignore_for_file: avoid_web_libraries_in_flutter
import 'dart:js_interop';
import 'login_challenge_loop_guard_base.dart';
@JS('window.sessionStorage')
external _JSStorage get _sessionStorage;
@JS()
extension type _JSStorage(JSObject _) implements JSObject {
external String? getItem(String key);
external void setItem(String key, String value);
external void removeItem(String key);
}
class _WebLoginChallengeLoopGuard implements LoginChallengeLoopGuard {
static const String _keyPrefix = 'baron_oidc_auto_accept_last:';
String _key(String challenge) => '$_keyPrefix$challenge';
@override
bool shouldAllowAutoAccept(String loginChallenge, {int cooldownMs = 15000}) {
final challenge = loginChallenge.trim();
if (challenge.isEmpty) {
return false;
}
try {
final raw = _sessionStorage.getItem(_key(challenge));
if (raw == null || raw.isEmpty) {
return true;
}
final lastMs = int.tryParse(raw);
if (lastMs == null) {
return true;
}
final nowMs = DateTime.now().millisecondsSinceEpoch;
return nowMs - lastMs > cooldownMs;
} catch (_) {
return true;
}
}
@override
void markAutoAcceptAttempt(String loginChallenge) {
final challenge = loginChallenge.trim();
if (challenge.isEmpty) {
return;
}
try {
final nowMs = DateTime.now().millisecondsSinceEpoch;
_sessionStorage.setItem(_key(challenge), nowMs.toString());
} catch (_) {}
}
@override
void clear(String loginChallenge) {
final challenge = loginChallenge.trim();
if (challenge.isEmpty) {
return;
}
try {
_sessionStorage.removeItem(_key(challenge));
} catch (_) {}
}
}
LoginChallengeLoopGuard createLoginChallengeLoopGuard() {
return _WebLoginChallengeLoopGuard();
}

View File

@@ -0,0 +1,39 @@
import '../notifiers/auth_notifier.dart';
import 'auth_proxy_service.dart';
import 'auth_token_store.dart';
typedef CurrentSessionLoader = Future<String?> Function();
typedef SessionRevoker = Future<void> Function(String sessionId);
typedef LogoutCallback = void Function();
class LogoutService {
LogoutService({
CurrentSessionLoader? loadCurrentSessionId,
SessionRevoker? revokeSession,
LogoutCallback? clearAuth,
LogoutCallback? notifyAuthChanged,
}) : _loadCurrentSessionId =
loadCurrentSessionId ?? AuthProxyService.fetchCurrentSessionId,
_revokeSession = revokeSession ?? AuthProxyService.revokeSession,
_clearAuth = clearAuth ?? AuthTokenStore.clear,
_notifyAuthChanged = notifyAuthChanged ?? AuthNotifier.instance.notify;
final CurrentSessionLoader _loadCurrentSessionId;
final SessionRevoker _revokeSession;
final LogoutCallback _clearAuth;
final LogoutCallback _notifyAuthChanged;
Future<void> logout() async {
try {
final currentSessionId = await _loadCurrentSessionId();
if (currentSessionId != null && currentSessionId.isNotEmpty) {
await _revokeSession(currentSessionId);
}
} catch (_) {
// 서버 세션 종료는 best-effort로 처리하고, 로컬 로그아웃은 계속 진행합니다.
} finally {
_clearAuth();
_notifyAuthChanged();
}
}
}

View File

@@ -0,0 +1,26 @@
import '../i18n/locale_utils.dart';
String? computeNullCheckRecoveryTarget({
required Object exception,
required Uri uri,
required String preferredLocaleCode,
}) {
final message = exception.toString();
if (!message.contains('Null check operator used on a null value')) {
return null;
}
final localeCode =
extractLocaleFromPath(uri) ?? normalizeLocaleCode(preferredLocaleCode);
final path = uri.path;
final localeRootPath = '/$localeCode';
if (path != '/' && path != localeRootPath) {
return null;
}
final target = '/$localeCode/signin';
if (path == target) {
return null;
}
return target;
}

View File

@@ -0,0 +1,220 @@
class OidcRedirectCheckResult {
final Uri? uri;
final bool isValid;
final String reason;
final int length;
final String scheme;
final String host;
final String path;
final int queryParamCount;
final List<String> queryKeys;
final bool hasLoginVerifier;
final int loginVerifierLength;
final bool hasState;
final int stateLength;
final bool hasClientId;
final String clientId;
final bool hasCodeChallenge;
final int codeChallengeLength;
final String codeChallengeMethod;
final bool hasRedirectUri;
final int redirectUriLength;
final String redirectUriScheme;
final String redirectUriHost;
final int redirectUriPort;
final String redirectUriPath;
final String responseType;
final int scopeCount;
final bool isOidcAuthPath;
const OidcRedirectCheckResult({
required this.uri,
required this.isValid,
required this.reason,
required this.length,
required this.scheme,
required this.host,
required this.path,
required this.queryParamCount,
required this.queryKeys,
required this.hasLoginVerifier,
required this.loginVerifierLength,
required this.hasState,
required this.stateLength,
required this.hasClientId,
required this.clientId,
required this.hasCodeChallenge,
required this.codeChallengeLength,
required this.codeChallengeMethod,
required this.hasRedirectUri,
required this.redirectUriLength,
required this.redirectUriScheme,
required this.redirectUriHost,
required this.redirectUriPort,
required this.redirectUriPath,
required this.responseType,
required this.scopeCount,
required this.isOidcAuthPath,
});
Map<String, Object?> toDiagnostics() {
return {
'is_valid': isValid,
'reason': reason,
'length': length,
'scheme': scheme,
'host': host,
'path': path,
'is_oidc_auth_path': isOidcAuthPath,
'query_param_count': queryParamCount,
'query_keys': queryKeys,
'has_login_verifier': hasLoginVerifier,
'login_verifier_len': loginVerifierLength,
'has_state': hasState,
'state_len': stateLength,
'has_client_id': hasClientId,
'client_id': clientId,
'has_code_challenge': hasCodeChallenge,
'code_challenge_len': codeChallengeLength,
'code_challenge_method': codeChallengeMethod,
'has_redirect_uri': hasRedirectUri,
'redirect_uri_len': redirectUriLength,
'redirect_uri_scheme': redirectUriScheme,
'redirect_uri_host': redirectUriHost,
'redirect_uri_port': redirectUriPort,
'redirect_uri_path': redirectUriPath,
'response_type': responseType,
'scope_count': scopeCount,
};
}
}
OidcRedirectCheckResult validateOidcRedirectTarget(String redirectTo) {
final trimmed = redirectTo.trim();
if (trimmed.isEmpty) {
return const OidcRedirectCheckResult(
uri: null,
isValid: false,
reason: 'empty',
length: 0,
scheme: '',
host: '',
path: '',
queryParamCount: 0,
queryKeys: [],
hasLoginVerifier: false,
loginVerifierLength: 0,
hasState: false,
stateLength: 0,
hasClientId: false,
clientId: '',
hasCodeChallenge: false,
codeChallengeLength: 0,
codeChallengeMethod: '',
hasRedirectUri: false,
redirectUriLength: 0,
redirectUriScheme: '',
redirectUriHost: '',
redirectUriPort: 0,
redirectUriPath: '',
responseType: '',
scopeCount: 0,
isOidcAuthPath: false,
);
}
Uri parsed;
try {
parsed = Uri.parse(trimmed);
} catch (_) {
return OidcRedirectCheckResult(
uri: null,
isValid: false,
reason: 'parse_error',
length: trimmed.length,
scheme: '',
host: '',
path: '',
queryParamCount: 0,
queryKeys: [],
hasLoginVerifier: false,
loginVerifierLength: 0,
hasState: false,
stateLength: 0,
hasClientId: false,
clientId: '',
hasCodeChallenge: false,
codeChallengeLength: 0,
codeChallengeMethod: '',
hasRedirectUri: false,
redirectUriLength: 0,
redirectUriScheme: '',
redirectUriHost: '',
redirectUriPort: 0,
redirectUriPath: '',
responseType: '',
scopeCount: 0,
isOidcAuthPath: false,
);
}
final scheme = parsed.scheme.toLowerCase();
final isHttpScheme = scheme == 'http' || scheme == 'https';
final isAbsolute = parsed.hasScheme && parsed.host.isNotEmpty;
final isValid = isHttpScheme && isAbsolute;
final query = parsed.queryParameters;
final queryKeys = query.keys.toList()..sort();
final loginVerifier = query['login_verifier'] ?? '';
final state = query['state'] ?? '';
final clientId = query['client_id'] ?? '';
final codeChallenge = query['code_challenge'] ?? '';
final codeChallengeMethod = query['code_challenge_method'] ?? '';
final redirectUriValue = query['redirect_uri'] ?? query['redirect_url'] ?? '';
final responseType = query['response_type'] ?? '';
final scope = query['scope'] ?? '';
final Uri? redirectUriParsed = redirectUriValue.isEmpty
? null
: Uri.tryParse(redirectUriValue);
final redirectUriScheme = redirectUriParsed?.scheme ?? '';
final redirectUriHost = redirectUriParsed?.host ?? '';
final redirectUriPort = redirectUriParsed?.port ?? 0;
final redirectUriPath = redirectUriParsed?.path ?? '';
final scopeCount = scope.isEmpty
? 0
: scope.split(RegExp(r'\s+')).where((s) => s.isNotEmpty).length;
final reason = isValid
? 'ok'
: (isAbsolute ? 'unsupported_scheme' : 'not_absolute');
return OidcRedirectCheckResult(
uri: isValid ? parsed : null,
isValid: isValid,
reason: reason,
length: trimmed.length,
scheme: scheme,
host: parsed.host,
path: parsed.path,
queryParamCount: query.length,
queryKeys: queryKeys,
hasLoginVerifier: loginVerifier.isNotEmpty,
loginVerifierLength: loginVerifier.length,
hasState: state.isNotEmpty,
stateLength: state.length,
hasClientId: clientId.isNotEmpty,
clientId: clientId,
hasCodeChallenge: codeChallenge.isNotEmpty,
codeChallengeLength: codeChallenge.length,
codeChallengeMethod: codeChallengeMethod,
hasRedirectUri: redirectUriValue.isNotEmpty,
redirectUriLength: redirectUriValue.length,
redirectUriScheme: redirectUriScheme,
redirectUriHost: redirectUriHost,
redirectUriPort: redirectUriPort,
redirectUriPath: redirectUriPath,
responseType: responseType,
scopeCount: scopeCount,
isOidcAuthPath: parsed.path == '/oidc/oauth2/auth',
);
}

View File

@@ -0,0 +1,37 @@
const _compileTimeEnv = {
'APP_ENV': String.fromEnvironment('APP_ENV'),
'BACKEND_URL': String.fromEnvironment('BACKEND_URL'),
'CLIENT_LOG_DEBUG': String.fromEnvironment('CLIENT_LOG_DEBUG'),
'USERFRONT_DEBUG_LOG': String.fromEnvironment('USERFRONT_DEBUG_LOG'),
'USERFRONT_URL': String.fromEnvironment('USERFRONT_URL'),
};
String runtimeOriginFallback() {
try {
final origin = Uri.base.origin;
if (origin.isNotEmpty && origin != 'null') {
return origin;
}
} catch (_) {}
return '';
}
String envOrDefault(String key, String fallback) {
final compileTimeValue = _compileTimeEnv[key];
if (compileTimeValue != null && compileTimeValue.trim().isNotEmpty) {
return compileTimeValue;
}
return fallback;
}
String sanitizedUrl(String value) {
return value.replaceAll(r'$', '').trim().replaceAll(RegExp(r'/$'), '');
}
String runtimeBackendUrl() {
return sanitizedUrl(envOrDefault('BACKEND_URL', runtimeOriginFallback()));
}
String runtimeUserfrontUrl() {
return sanitizedUrl(envOrDefault('USERFRONT_URL', runtimeOriginFallback()));
}

View File

@@ -0,0 +1,13 @@
import 'web_auth_integration_stub.dart'
if (dart.library.js_interop) 'web_auth_integration_web.dart';
abstract class WebAuthIntegration {
static void sendLoginSuccess(String token) {
// Platform-specific implementation
implSendLoginSuccess(token);
}
static bool isPopup() {
return implIsPopup();
}
}

Some files were not shown because too many files have changed in this diff Show More