main #21

Merged
seonkyu.kim merged 6 commits from seonkyu.kim/AcaMate_Web:main into debug 2025-06-17 07:19:18 +00:00
15 changed files with 471 additions and 61 deletions
Showing only changes of commit 9621169d57 - Show all commits

View File

@ -8,17 +8,17 @@ namespace Front;
public partial class App : ComponentBase public partial class App : ComponentBase
{ {
[Inject] private APIService API { get; set; } = default!; [Inject] private APIService API { get; set; } = default!;
[Inject] private CookieService Cookie { get; set; } = default!; [Inject] private StorageService StorageService { get; set; } = default!;
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
{ {
var cookie = await Cookie.GetCookieAsync("Web_AM_Connect_Key"); var headerValue = await StorageService.GetItemAsync("Web_AM_Connect_Key");
// 값 없으면 API 호출 // 값 없으면 API 호출
if (string.IsNullOrEmpty(cookie)) if (string.IsNullOrEmpty(headerValue))
{ {
var response = await API.GetJsonAsync<APIHeader, AppHeader>( var response = await API.GetJsonAsync<APIHeader, AppHeader>(
"/api/v1/in/app", "/api/v1/in/app",
@ -30,7 +30,7 @@ public partial class App : ComponentBase
}); });
if (!string.IsNullOrEmpty(response.data.header)) if (!string.IsNullOrEmpty(response.data.header))
{ {
await Cookie.SetCookieAsync("Web_AM_Connect_Key", response.data.header); await StorageService.SetItemAsync("Web_AM_Connect_Key", response.data.header);
} }
} }
} }

View File

@ -10,6 +10,9 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after"); builder.RootComponents.Add<HeadOutlet>("head::after");
// 설정 파일 로드
// builder.Configuration.AddJsonFile("appsettings.json", optional: false);
builder.Configuration.AddJsonFile($"appsettings.{builder.HostEnvironment.Environment}.json", optional: true);
builder.Services.AddScoped(sp => //new HttpClient builder.Services.AddScoped(sp => //new HttpClient
{ {
@ -30,7 +33,8 @@ builder.Services.AddScoped(sp => //new HttpClient
// SCOPED 으로 등록된 서비스는 DI 컨테이너에 등록된 서비스의 인스턴스를 사용합니다. // SCOPED 으로 등록된 서비스는 DI 컨테이너에 등록된 서비스의 인스턴스를 사용합니다.
builder.Services.AddScoped<APIService>(); builder.Services.AddScoped<APIService>();
builder.Services.AddScoped<CookieService>(); builder.Services.AddScoped<SecureService>();
builder.Services.AddScoped<StorageService>();
// builder.Services.AddRazorPages(); // builder.Services.AddRazorPages();
// builder.Services.AddServerSideBlazor(); // builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<LoadingService>(); builder.Services.AddScoped<LoadingService>();

View File

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Components.Routing;
using Front.Program.Views.Project; using Front.Program.Views.Project;
using Front.Program.Services; using Front.Program.Services;
@ -13,6 +14,9 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
[Inject] [Inject]
LoadingService LoadingService { get; set; } = default!; LoadingService LoadingService { get; set; } = default!;
[Inject]
StorageService StorageService { get; set; } = default!;
// 경로의 시작 부분 // 경로의 시작 부분
// protected bool isHidePrjTop => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("auth", StringComparison.OrdinalIgnoreCase); // protected bool isHidePrjTop => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("auth", StringComparison.OrdinalIgnoreCase);
@ -23,11 +27,43 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
{ {
LoadingService.OnChange += StateHasChanged; LoadingService.OnChange += StateHasChanged;
Navigation.LocationChanged += HandleLocationChanged; Navigation.LocationChanged += HandleLocationChanged;
HandleLocationChanged(this, new LocationChangedEventArgs(Navigation.Uri, false));
} }
private void HandleLocationChanged(object? sender, LocationChangedEventArgs e) private async void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
{ {
LoadingService.HideNavigationLoading(); LoadingService.HideNavigationLoading();
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
Console.WriteLine($"리다이렉트된 URI: {uri}");
if (uri.Query.Contains("auth="))
{
var query = uri.Query.TrimStart('?');
var parameters = query.Split('&')
.Select(p => p.Split('='))
.Where(p => p.Length == 2)
.ToDictionary(p => p[0], p => p[1]);
if (parameters.TryGetValue("auth", out var auth))
{
Console.WriteLine($"auth 파라미터 값: {auth}");
if (auth == "true")
{
await StorageService.SetItemAsync("IsLogin", "true");
Console.WriteLine("로그인 상태를 true로 설정했습니다.");
}
else
{
await StorageService.RemoveItemAsync("IsLogin");
Console.WriteLine("로그인 상태를 제거했습니다.");
}
// 파라미터를 제거하고 리다이렉트
var baseUri = uri.GetLeftPart(UriPartial.Path);
Navigation.NavigateTo(baseUri, forceLoad: false);
}
}
} }
public void Dispose() public void Dispose()

View File

@ -29,3 +29,14 @@ public class APIService
return response; return response;
} }
} }
/*
dynamic :
Register.razor.cs에서 JsonElement를 . :
JSON (TryGetProperty )
*/

View File

@ -1,29 +0,0 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace Front.Program.Services;
public class CookieService
{
private readonly IJSRuntime _js;
public CookieService(IJSRuntime js)
{
_js = js;
}
public async Task SetCookieAsync(string key, string value)
{
await _js.InvokeVoidAsync("setCookie", key, value, 1);
}
public async Task<string?> GetCookieAsync(string key)
{
return await _js.InvokeAsync<string>("getCookie", key);
}
public async Task DeleteCookieAsync(string key)
{
await _js.InvokeVoidAsync("deleteCookie", key);
}
}

View File

@ -0,0 +1,120 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.JSInterop;
using System.Text.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
namespace Front.Program.Services;
public class SecureService
{
private readonly ILogger<SecureService> _logger;
private readonly IJSRuntime _jsRuntime;
private readonly IWebAssemblyHostEnvironment _environment;
private string? _key;
private string? _iv;
private Task? _initializationTask;
public SecureService(
ILogger<SecureService> logger,
IJSRuntime jsRuntime,
IWebAssemblyHostEnvironment environment)
{
_logger = logger;
_jsRuntime = jsRuntime;
_environment = environment;
_initializationTask = InitializeAsync();
}
private async Task InitializeAsync()
{
try
{
var configFile = $"appsettings.{_environment.Environment}.json";
_logger.LogInformation($"설정 파일 로드: {configFile}");
var config = await _jsRuntime.InvokeAsync<JsonElement>("loadConfig", configFile);
if (config.ValueKind == JsonValueKind.Null)
{
throw new InvalidOperationException($"설정 파일을 로드할 수 없습니다: {configFile}");
}
var security = config.GetProperty("Security");
_key = security.GetProperty("EncryptionKey").GetString()
?? throw new ArgumentNullException("Security:EncryptionKey");
_iv = security.GetProperty("EncryptionIV").GetString()
?? throw new ArgumentNullException("Security:EncryptionIV");
}
catch (Exception ex)
{
_logger.LogError($"설정 로드 중 오류 발생: {ex.Message}");
throw;
}
}
private async Task EnsureInitializedAsync()
{
if (_initializationTask != null)
{
await _initializationTask;
_initializationTask = null;
}
}
private byte[] GetKeyBytes(string key)
{
using (var sha256 = SHA256.Create())
{
return sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
}
}
private byte[] GetIVBytes(string iv)
{
using (var sha256 = SHA256.Create())
{
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(iv));
return hash.Take(16).ToArray(); // IV는 16바이트만 필요
}
}
public async Task<string> EncryptAsync(string plainText)
{
await EnsureInitializedAsync();
if (_key == null || _iv == null)
throw new InvalidOperationException("암호화 키가 초기화되지 않았습니다.");
try
{
return await _jsRuntime.InvokeAsync<string>("encryptText", plainText, _key, _iv);
}
catch (Exception ex)
{
_logger.LogError($"암호화 중 오류 발생: {ex.Message}");
throw;
}
}
public async Task<string> DecryptAsync(string cipherText)
{
await EnsureInitializedAsync();
if (_key == null || _iv == null)
throw new InvalidOperationException("암호화 키가 초기화되지 않았습니다.");
try
{
return await _jsRuntime.InvokeAsync<string>("decryptText", cipherText, _key, _iv);
}
catch (Exception ex)
{
_logger.LogError($"복호화 중 오류 발생: {ex.Message}");
throw;
}
}
// 동기 메서드는 비동기 메서드를 호출하도록 수정
public string Encrypt(string plainText) => EncryptAsync(plainText).GetAwaiter().GetResult();
public string Decrypt(string cipherText) => DecryptAsync(cipherText).GetAwaiter().GetResult();
}

View File

@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace Front.Program.Services;
public enum STORAGE_TYPE
{
Cookie,
Local,
Session
}
public class StorageService
{
private readonly IJSRuntime _js;
public StorageService(IJSRuntime js)
{
_js = js;
}
public async Task SetItemAsync(string key, string value, STORAGE_TYPE type = STORAGE_TYPE.Session, int expire_time = 1)
{
await (type switch
{
STORAGE_TYPE.Cookie => _js.InvokeVoidAsync("setCookie", key, value, expire_time),
STORAGE_TYPE.Local => _js.InvokeVoidAsync("localStorage.setItem", key, value),
STORAGE_TYPE.Session => _js.InvokeVoidAsync("sessionStorage.setItem", key, value),
_ => throw new ArgumentException("Invalid storage type")
});
}
public async Task<string?> GetItemAsync(string key, STORAGE_TYPE type = STORAGE_TYPE.Session)
{
return type switch
{
STORAGE_TYPE.Cookie => await _js.InvokeAsync<string>("getCookie", key),
STORAGE_TYPE.Local => await _js.InvokeAsync<string>("localStorage.getItem", key),
STORAGE_TYPE.Session => await _js.InvokeAsync<string>("sessionStorage.getItem", key),
_ => null
};
}
public async Task RemoveItemAsync(string key, STORAGE_TYPE type = STORAGE_TYPE.Session)
{
await (type switch
{
STORAGE_TYPE.Cookie => _js.InvokeVoidAsync("deleteCookie", key),
STORAGE_TYPE.Local => _js.InvokeVoidAsync("localStorage.removeItem", key),
STORAGE_TYPE.Session => _js.InvokeVoidAsync("sessionStorage.removeItem", key),
_ => throw new ArgumentException("Invalid storage type")
});
}
}
/*
1. Cookie
: ( )
:
1. HTTP / .
2. HttpOnly나 secure .
2. Local
:
: JS
1. .
2. .
3. Sesson
: / ( )
: JS
1. / .
2. .
*/

View File

@ -16,6 +16,7 @@ public partial class Register : ComponentBase
[Inject] private HttpClient Http { get; set; } = default!; [Inject] private HttpClient Http { get; set; } = default!;
[Inject] private IConfiguration Configuration { get; set; } = default!; [Inject] private IConfiguration Configuration { get; set; } = default!;
[Inject] private LoadingService LoadingService { get; set; } = default!; [Inject] private LoadingService LoadingService { get; set; } = default!;
[Inject] private StorageService CookieService { get; set; } = default!;
private ElementReference dateInputRef; private ElementReference dateInputRef;
@ -39,8 +40,7 @@ public partial class Register : ComponentBase
objRef = DotNetObjectReference.Create(this); objRef = DotNetObjectReference.Create(this);
try try
{ {
// 쿠키에서 토큰 가져오기 var token = await CookieService.GetItemAsync("Web_AM_Connect_Key");
var token = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('Web_AM_Connect_Key='))?.split('=')[1]");
if (string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))
{ {
await JS.InvokeVoidAsync("alert", "인증 정보가 없습니다."); await JS.InvokeVoidAsync("alert", "인증 정보가 없습니다.");
@ -169,8 +169,7 @@ public partial class Register : ComponentBase
try try
{ {
LoadingService.ShowLoading(); LoadingService.ShowLoading();
// 쿠키에서 토큰 가져오기 var token = await CookieService.GetItemAsync("Web_AM_Connect_Key");
var token = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('Web_AM_Connect_Key='))?.split('=')[1] || ''");
Console.WriteLine($"쿠키에서 가져온 토큰: '{token}'"); Console.WriteLine($"쿠키에서 가져온 토큰: '{token}'");
if (string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))

View File

@ -11,7 +11,7 @@
<a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/join" @onclick="() => isOpen = !isOpen">Join</a> <a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/join" @onclick="() => isOpen = !isOpen">Join</a>
<a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/new" @onclick="() => isOpen = !isOpen">What's New</a> <a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/new" @onclick="() => isOpen = !isOpen">What's New</a>
</div> </div>
@if (!isLoggedIn) @if (!isLogin)
{ {
<button class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-second-normal hover:bg-second-dark text-white text-sm font-bold leading-normal tracking-[0.015em] mr-4" <button class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-second-normal hover:bg-second-dark text-white text-sm font-bold leading-normal tracking-[0.015em] mr-4"
@onclick="OnClickLogin"> @onclick="OnClickLogin">
@ -38,7 +38,7 @@
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/about" @onclick="() => isOpen = !isOpen">About</a> <a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/about" @onclick="() => isOpen = !isOpen">About</a>
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/join" @onclick="() => isOpen = !isOpen">Join</a> <a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/join" @onclick="() => isOpen = !isOpen">Join</a>
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/new" @onclick="() => isOpen = !isOpen">What's New</a> <a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/new" @onclick="() => isOpen = !isOpen">What's New</a>
@if (!isLoggedIn) @if (!isLogin)
{ {
<button class="flex w-full cursor-pointer items-center justify-center rounded-lg h-10 px-4 bg-second-normal hover:bg-second-dark text-white text-sm font-bold leading-normal tracking-[0.015em]" <button class="flex w-full cursor-pointer items-center justify-center rounded-lg h-10 px-4 bg-second-normal hover:bg-second-dark text-white text-sm font-bold leading-normal tracking-[0.015em]"
@onclick="OnClickLogin"> @onclick="OnClickLogin">

View File

@ -1,24 +1,123 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using System;
using System.Net.Http.Json;
using System.Text.Json;
using Front.Program.Services;
namespace Front.Program.Views.Project; namespace Front.Program.Views.Project;
public partial class TopProjectNav : ComponentBase public partial class TopProjectNav : ComponentBase
{ {
[Inject] [Inject] NavigationManager NavigationManager { get; set; } = default!;
NavigationManager NavigationManager { get; set; } = default!; [Inject] StorageService StorageService { get; set; } = default!;
[Inject] SecureService SecureService { get; set; } = default!;
[Inject] [Inject] IJSRuntime JS { get; set; } = default!;
IJSRuntime JS { get; set; } = default!;
[Inject] HttpClient Http { get; set; } = default!;
string UserName { get; set; } = default!;
protected bool isOpen = false; protected bool isOpen = false;
protected bool isLoggedIn = false; bool isLogin = false;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// 쿠키에서 로그인 상태 확인 Console.WriteLine("TopNAV 확인");
var isLoginCookie = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('IsLogin='))?.split('=')[1]"); isLogin = isLogin = bool.TryParse(await StorageService.GetItemAsync("IsLogin"), out var result) && result;
isLoggedIn = isLoginCookie == "true"; if (isLogin) return;
try
{
var encryptedName = await StorageService.GetItemAsync("USER");
Console.WriteLine($"{encryptedName}");
if (!string.IsNullOrEmpty(encryptedName))
{
try
{
UserName = await SecureService.DecryptAsync(encryptedName);
Console.WriteLine($"세션 스토리지에서 가져온 사용자 이름: '{UserName}'");
return;
}
catch (Exception ex)
{
Console.WriteLine($"이름 복호화 중 오류 발생: {ex.Message}");
await StorageService.RemoveItemAsync("USER");
}
}
// apiSender.js의 함수를 사용하여 연결 키 가져오기
var headerValue = await StorageService.GetItemAsync("Web_AM_Connect_Key");
Console.WriteLine($"세션 스토리지에서 가져온 헤더 값: '{headerValue}'");
if (string.IsNullOrEmpty(headerValue))
{
Console.WriteLine("연결 키가 없습니다");
return;
}
// apiSender.js의 함수를 사용하여 API 호출
Console.WriteLine("세션 API 호출 시작");
var response = await JS.InvokeAsync<JsonElement>("fetchWithHeaderAndReturnUrl",
"/api/v1/in/user/auth/session",
"GET",
"Web_AM_Connect_Key",
headerValue);
Console.WriteLine($"세션 API 응답 타입: {response.ValueKind}");
Console.WriteLine($"세션 API 응답 내용: {response}");
if (response.ValueKind == JsonValueKind.Null || response.ValueKind == JsonValueKind.Undefined)
{
Console.WriteLine("응답이 null이거나 undefined입니다");
return;
}
try
{
if (response.TryGetProperty("status", out var statusElement))
{
Console.WriteLine($"status 요소 타입: {statusElement.ValueKind}");
if (statusElement.TryGetProperty("code", out var codeElement))
{
var code = codeElement.GetString();
Console.WriteLine($"응답 코드: {code}");
if (code == "000")
{
if (response.TryGetProperty("data", out var dataElement))
{
Console.WriteLine($"data 요소 타입: {dataElement.ValueKind}");
if (dataElement.TryGetProperty("name", out var nameElement))
{
UserName = nameElement.GetString() ?? "이름 없음";
isLogin = true;
Console.WriteLine($"NM: {UserName}");
var encryptedUserName = await SecureService.EncryptAsync(UserName);
Console.WriteLine($"NM: {encryptedUserName}");
await StorageService.SetItemAsync("USER", encryptedUserName);
await StorageService.SetItemAsync("Web_AM_Connect_Key", headerValue);
Console.WriteLine($"로그인된 사용자: {UserName}");
return;
}
}
}
}
}
Console.WriteLine("로그인되지 않은 상태");
}
catch (Exception ex)
{
Console.WriteLine($"응답 처리 중 오류 발생: {ex.Message}");
Console.WriteLine($"응답 처리 스택 트레이스: {ex.StackTrace}");
}
}
catch (Exception ex)
{
Console.WriteLine($"세션 확인 중 오류 발생: {ex.Message}");
Console.WriteLine($"스택 트레이스: {ex.StackTrace}");
}
} }
public void OnClickMenuDown() public void OnClickMenuDown()

13
appsettings.json Normal file
View File

@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Security": {
"EncryptionKey": "AcaMate2025SecureKeySeanForEncryption19940509",
"EncryptionIV": "AcaMate2025IV9459"
}
}

View File

@ -1,3 +1,7 @@
{ {
"ApiBaseUrl": "https://devacamate.ipstein.myds.me/" "ApiBaseUrl": "https://devacamate.ipstein.myds.me/",
"Security": {
"EncryptionKey": "AcaMate2025SecureKeyForEncryptionSean1994",
"EncryptionIV": "AcaMate2025IV9459"
}
} }

View File

@ -1,3 +1,7 @@
{ {
"ApiBaseUrl": "https://acamate.ipstein.myds.me/" "ApiBaseUrl": "https://acamate.ipstein.myds.me/",
"Security": {
"EncryptionKey": "AcaMate2025SecureKeyForEncryptionSean1994",
"EncryptionIV": "AcaMate2025IV9459"
}
} }

View File

@ -70,6 +70,7 @@
const v = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)'); const v = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
return v ? v.pop() : ''; return v ? v.pop() : '';
}; };
window.setCookie = function (name, value, days) { window.setCookie = function (name, value, days) {
let expires = ""; let expires = "";
if (days) { if (days) {

View File

@ -12,12 +12,85 @@ window.postWithHeader = function(url, method, headerKey, headerValue) {
}; };
window.fetchWithHeaderAndReturnUrl = async function(url, method, headerKey, headerValue) { window.fetchWithHeaderAndReturnUrl = async function(url, method, headerKey, headerValue) {
const response = await fetch(url, { try {
method: method, const response = await fetch(url, {
headers: { method: method,
[headerKey]: headerValue headers: {
[headerKey]: headerValue
}
});
if (!response.ok) {
console.error('API 호출 실패:', response.status, response.statusText);
return null;
} }
});
const data = await response.json(); const data = await response.json();
return data.url; console.log('API 응답 데이터:', data);
return data;
} catch (error) {
console.error('API 호출 중 오류 발생:', error);
return null;
}
};
window.loadConfig = async function(configFile) {
try {
console.log('설정 파일 로드 시도:', configFile);
const response = await fetch(configFile);
if (!response.ok) {
console.error('설정 파일 로드 실패:', response.status, response.statusText);
return null;
}
const config = await response.json();
console.log('설정 파일 로드 성공:', configFile);
return config;
} catch (error) {
console.error('설정 파일 로드 중 오류 발생:', error);
return null;
}
};
window.encryptText = async function(text, key, iv) {
try {
// XOR 암호화 구현
let result = '';
for (let i = 0; i < text.length; i++) {
const charCode = text.charCodeAt(i) ^ key.charCodeAt(i % key.length);
result += String.fromCharCode(charCode);
}
// UTF-8로 인코딩 후 Base64로 변환
const utf8Encoder = new TextEncoder();
const bytes = utf8Encoder.encode(result);
const base64 = btoa(String.fromCharCode.apply(null, bytes));
return base64;
} catch (error) {
console.error('암호화 중 오류 발생:', error);
throw error;
}
};
window.decryptText = async function(encryptedText, key, iv) {
try {
// Base64 디코딩 후 UTF-8 디코딩
const binary = atob(encryptedText);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
const utf8Decoder = new TextDecoder();
const text = utf8Decoder.decode(bytes);
// XOR 복호화
let result = '';
for (let i = 0; i < text.length; i++) {
const charCode = text.charCodeAt(i) ^ key.charCodeAt(i % key.length);
result += String.fromCharCode(charCode);
}
return result;
} catch (error) {
console.error('복호화 중 오류 발생:', error);
throw error;
}
}; };