fix: stabilize admin login and ci versioning
TaxBaik CI/CD / build-and-deploy (push) Failing after 1m40s
TaxBaik CI/CD / build-and-deploy (push) Failing after 1m40s
This commit is contained in:
@@ -83,7 +83,6 @@ jobs:
|
|||||||
BUILD_TIME=$(date -u +'%Y-%m-%d %H:%M:%S UTC')
|
BUILD_TIME=$(date -u +'%Y-%m-%d %H:%M:%S UTC')
|
||||||
mkdir -p ./publish/wwwroot
|
mkdir -p ./publish/wwwroot
|
||||||
printf '{\n "version": "%s",\n "built": "%s"\n}\n' "$COMMIT_HASH" "$BUILD_TIME" > ./publish/wwwroot/version.json
|
printf '{\n "version": "%s",\n "built": "%s"\n}\n' "$COMMIT_HASH" "$BUILD_TIME" > ./publish/wwwroot/version.json
|
||||||
printf 'Version: %s\nBuilt: %s\n' "$COMMIT_HASH" "$BUILD_TIME" > ./publish/wwwroot/version.txt
|
|
||||||
echo "✓ Build: $COMMIT_HASH @ $BUILD_TIME"
|
echo "✓ Build: $COMMIT_HASH @ $BUILD_TIME"
|
||||||
|
|
||||||
- name: Setup SSH
|
- name: Setup SSH
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MudThemeProvider @bind-IsDarkMode="isDarkMode" Theme="mudTheme" />
|
<MudThemeProvider @bind-IsDarkMode="isDarkMode" Theme="mudTheme" />
|
||||||
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />
|
<Routes @rendermode="new InteractiveServerRenderMode(prerender: true)" />
|
||||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||||
<script src="js/admin-session.js"></script>
|
<script src="js/admin-session.js"></script>
|
||||||
<script src="_framework/blazor.web.js"></script>
|
<script src="_framework/blazor.web.js"></script>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
@page "/admin/login"
|
@page "/admin/login"
|
||||||
@layout TaxBaik.Web.Components.Admin.Layout.BlankLayout
|
@layout TaxBaik.Web.Components.Admin.Layout.BlankLayout
|
||||||
@attribute [AllowAnonymous]
|
@attribute [AllowAnonymous]
|
||||||
|
@rendermode @(new InteractiveServerRenderMode(prerender: true))
|
||||||
|
@inject IApiClient ApiClient
|
||||||
@inject ILocalStorageService LocalStorageService
|
@inject ILocalStorageService LocalStorageService
|
||||||
@inject IJSRuntime Js
|
@inject IJSRuntime Js
|
||||||
|
|
||||||
@@ -10,23 +12,23 @@
|
|||||||
<MudPaper Class="pa-8" Elevation="3" Style="width: 100%; max-width: 400px;">
|
<MudPaper Class="pa-8" Elevation="3" Style="width: 100%; max-width: 400px;">
|
||||||
<MudText Typo="Typo.h4" Class="mb-6 text-center">관리자 로그인</MudText>
|
<MudText Typo="Typo.h4" Class="mb-6 text-center">관리자 로그인</MudText>
|
||||||
|
|
||||||
<form id="admin-login-form" method="post" action="/taxbaik/admin/login">
|
<EditForm Model="model" OnValidSubmit="HandleLogin">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
<input class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start mb-4"
|
<input class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start mb-4"
|
||||||
style="width: 100%; min-height: 56px; padding: 16px 14px;"
|
style="width: 100%; min-height: 56px; padding: 16px 14px;"
|
||||||
placeholder="사용자명"
|
placeholder="사용자명"
|
||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
name="username"
|
@bind="model.Username" />
|
||||||
value="@model.Username" />
|
|
||||||
|
|
||||||
<input type="password"
|
<input type="password"
|
||||||
class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start mb-4"
|
class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start mb-4"
|
||||||
style="width: 100%; min-height: 56px; padding: 16px 14px;"
|
style="width: 100%; min-height: 56px; padding: 16px 14px;"
|
||||||
placeholder="비밀번호"
|
placeholder="비밀번호"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
name="password" />
|
@bind="model.Password" />
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<input class="mud-checkbox" type="checkbox" name="rememberMe" />
|
<input class="mud-checkbox" type="checkbox" @bind="model.RememberMe" />
|
||||||
<label style="margin-left: 8px; cursor: pointer;">아이디 저장</label>
|
<label style="margin-left: 8px; cursor: pointer;">아이디 저장</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -37,12 +39,12 @@
|
|||||||
style="width: 100%; min-height: 52px; border: 0; border-radius: 4px; color: white;">
|
style="width: 100%; min-height: 52px; border: 0; border-radius: 4px; color: white;">
|
||||||
<span>로그인</span>
|
<span>로그인</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</EditForm>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private readonly LoginModel model = new();
|
private LoginModel model = new();
|
||||||
private const string RememberedUsernameKey = "admin-remembered-username";
|
private const string RememberedUsernameKey = "admin-remembered-username";
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@@ -64,14 +66,45 @@
|
|||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
|
||||||
await Js.InvokeVoidAsync("taxbaikAdminSession.syncRouteClass");
|
await Js.InvokeVoidAsync("taxbaikAdminSession.syncRouteClass");
|
||||||
await Js.InvokeVoidAsync("taxbaikAdminSession.bindLoginForm");
|
}
|
||||||
|
|
||||||
|
private async Task HandleLogin()
|
||||||
|
{
|
||||||
|
var request = new { model.Username, model.Password };
|
||||||
|
var response = await ApiClient.PostAsync<LoginResponse>("auth/login", request);
|
||||||
|
|
||||||
|
if (response?.AccessToken == null || response?.RefreshToken == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model.RememberMe)
|
||||||
|
{
|
||||||
|
await LocalStorageService.SetItemAsStringAsync(RememberedUsernameKey, model.Username);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await LocalStorageService.RemoveItemAsync(RememberedUsernameKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Js.InvokeVoidAsync("localStorage.setItem", "accessToken", response.AccessToken);
|
||||||
|
await Js.InvokeVoidAsync("localStorage.setItem", "refreshToken", response.RefreshToken);
|
||||||
|
await Js.InvokeVoidAsync("localStorage.setItem", "tokenExpiry", DateTimeOffset.UtcNow.AddSeconds(response.ExpiresIn).ToUnixTimeMilliseconds().ToString());
|
||||||
|
await Js.InvokeVoidAsync("window.location.assign", "/taxbaik/admin/dashboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoginModel
|
private class LoginModel
|
||||||
{
|
{
|
||||||
public string Username { get; set; } = "";
|
public string Username { get; set; } = "";
|
||||||
|
public string Password { get; set; } = "";
|
||||||
|
public bool RememberMe { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LoginResponse
|
||||||
|
{
|
||||||
|
public string AccessToken { get; set; } = "";
|
||||||
|
public string RefreshToken { get; set; } = "";
|
||||||
|
public int ExpiresIn { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,61 +110,6 @@ window.taxbaikAdminSession = {
|
|||||||
|
|
||||||
new MutationObserver(reloadOnRejectedCircuit)
|
new MutationObserver(reloadOnRejectedCircuit)
|
||||||
.observe(modal, { attributes: true, attributeFilter: ['class'] });
|
.observe(modal, { attributes: true, attributeFilter: ['class'] });
|
||||||
},
|
|
||||||
|
|
||||||
bindLoginForm: function () {
|
|
||||||
const form = document.getElementById('admin-login-form');
|
|
||||||
if (!form || form.dataset.bound === '1') return;
|
|
||||||
|
|
||||||
form.dataset.bound = '1';
|
|
||||||
form.addEventListener('submit', async function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const username = form.querySelector('input[placeholder="사용자명"]')?.value?.trim() || '';
|
|
||||||
const password = form.querySelector('input[placeholder="비밀번호"]')?.value || '';
|
|
||||||
const rememberMe = form.querySelector('input[type="checkbox"]')?.checked || false;
|
|
||||||
const existing = form.parentElement.querySelector('.login-error-message');
|
|
||||||
const submitButton = form.querySelector('button[type="submit"]');
|
|
||||||
|
|
||||||
if (existing) existing.remove();
|
|
||||||
if (submitButton) submitButton.disabled = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/taxbaik/api/auth/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ username, password })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('login failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
if (!data?.accessToken || !data?.refreshToken) {
|
|
||||||
throw new Error('invalid response');
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem('accessToken', data.accessToken);
|
|
||||||
localStorage.setItem('refreshToken', data.refreshToken);
|
|
||||||
localStorage.setItem('tokenExpiry', String(Date.now() + (data.expiresIn || 3600) * 1000));
|
|
||||||
|
|
||||||
if (rememberMe) {
|
|
||||||
localStorage.setItem('admin-remembered-username', username);
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem('admin-remembered-username');
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.href = '/taxbaik/admin/dashboard';
|
|
||||||
} catch {
|
|
||||||
const error = document.createElement('div');
|
|
||||||
error.className = 'mud-alert mud-alert-filled-error login-error-message mb-4';
|
|
||||||
error.textContent = '로그인 중 오류가 발생했습니다.';
|
|
||||||
form.parentElement.insertBefore(error, form);
|
|
||||||
} finally {
|
|
||||||
if (submitButton) submitButton.disabled = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user