diff --git a/Projects/Config/WebApi.AuthApi.Config.json b/Projects/Config/WebApi.AuthApi.Config.json
new file mode 100644
index 0000000..d1ab4d3
--- /dev/null
+++ b/Projects/Config/WebApi.AuthApi.Config.json
@@ -0,0 +1,35 @@
+{
+ "Server": {
+ "Address": "https://*",
+ "Port": 11000,
+ "IIS": false
+ },
+ "Auth": {
+ "issuer": "SystemX.WebApi.Auth",
+ "audience": "AuthApi",
+ "accessTokenSecret": "t6zdogyrT0U1bYw3gJvMm3JHmj2Iyawr7O2WKE2truX+MK0l/XNGmpU2ofagdUWBN4DxAUv7c8xSYVv/8abL6A==",
+ "accessTokenExpires": 1440, //minutes
+ "refreshTokenSecret": "1vVuoGqIqkStFI3QUXHMr0/yO1feLPnhqcfFGjZyk478+4WY7dhrUjCfVeWjmmSZYgb+rtP0X6ec+3iL35Yezw==",
+ "refreshTokenExpires": 1440 //minuts, 60*24 (1day)
+ },
+ "DataBase": [
+ {
+ "IP": "127.0.0.1",
+ "Port": 1433,
+ "DBName": "AccountDB",
+ "DBID": 1,
+ "DBContext": "AccountDB",
+ "UserID": "SystemX",
+ "Password": "X"
+ },
+ {
+ "IP": "127.0.0.1",
+ "Port": 1433,
+ "DBName": "AccountDB_DEV",
+ "DBID": 2,
+ "DBContext": "AccountDB",
+ "UserID": "SystemX",
+ "Password": "X"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Projects/Config/log4net.config b/Projects/Config/log4net.config
new file mode 100644
index 0000000..b970851
--- /dev/null
+++ b/Projects/Config/log4net.config
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Projects/DLL/SystemX.Core.dll b/Projects/DLL/SystemX.Core.dll
index 44c0858..5ab830f 100644
Binary files a/Projects/DLL/SystemX.Core.dll and b/Projects/DLL/SystemX.Core.dll differ
diff --git a/Projects/HubX/DBPatch/sqlScripts/dacpac/HubX.DB.dacpac b/Projects/HubX/DBPatch/sqlScripts/dacpac/HubX.DB.dacpac
index 86a3ba6..56825d6 100644
Binary files a/Projects/HubX/DBPatch/sqlScripts/dacpac/HubX.DB.dacpac and b/Projects/HubX/DBPatch/sqlScripts/dacpac/HubX.DB.dacpac differ
diff --git a/Projects/HubX/HubX.Library.DB/DB/HubX/Context/HubXContext.cs b/Projects/HubX/HubX.Library.DB/DB/HubX/Context/HubXContext.cs
index a125fdb..488d700 100644
--- a/Projects/HubX/HubX.Library.DB/DB/HubX/Context/HubXContext.cs
+++ b/Projects/HubX/HubX.Library.DB/DB/HubX/Context/HubXContext.cs
@@ -19,7 +19,7 @@ public partial class HubXContext : DbContext
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
- => optionsBuilder.UseSqlServer("server=127.0.0.1; user id=VPKI; password=Kefico!@34; database=HubX; TrustServerCertificate=true;");
+ => optionsBuilder.UseSqlServer("server=127.0.0.1; user id=alis; password=Kefico!@34; database=HubX; TrustServerCertificate=true;");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
diff --git a/Projects/HubX/HubX.Server/Controllers/UniqueKeyController.cs b/Projects/HubX/HubX.Server/Controllers/UniqueKeyController.cs
index 2168f24..6e8bf6e 100644
--- a/Projects/HubX/HubX.Server/Controllers/UniqueKeyController.cs
+++ b/Projects/HubX/HubX.Server/Controllers/UniqueKeyController.cs
@@ -1,4 +1,5 @@
-using HubX.Library.Http.Packet;
+using Azure.Core;
+using HubX.Library.Http.Packet;
using HubX.Server.Services;
using Microsoft.AspNetCore.Mvc;
@@ -47,6 +48,30 @@ namespace HubX.Server.Controllers
return Results.Ok(res);
}
+ [HttpGet]
+ public async Task SelectUniqueKeyGet([FromQuery] string key)
+ {
+ var guid = Guid.NewGuid();
+ // Log4net.WriteLine($"[Requeust]({guid}) UniqueKey/SelectUniqueKey::{request.ToJson()}", LogType.CONTROLLER);
+
+ Response_SelectUniqueKy res = await _uniqueKeyService.Request_SelectUniqueKey(new Request_SelectUniqueKey { Identity = key } );
+ // Log4net.WriteLine($"[Response]({guid}) UniqueKey/SelectUniqueKey::{res.ToJson()}", LogType.CONTROLLER);
+
+ return Results.Ok(res);
+ }
+
+ [HttpGet]
+ public async Task SelectUniqueKeyGetAll()
+ {
+ var guid = Guid.NewGuid();
+ // Log4net.WriteLine($"[Requeust]({guid}) UniqueKey/SelectUniqueKey::{request.ToJson()}", LogType.CONTROLLER);
+
+ var res = await _uniqueKeyService.Request_SelectUniqueKeyAll();
+ // Log4net.WriteLine($"[Response]({guid}) UniqueKey/SelectUniqueKey::{res.ToJson()}", LogType.CONTROLLER);
+
+ return Results.Ok(res);
+ }
+
[HttpPost]
public async Task UpdateUniqueKey(Request_UpdateUniqueKey request)
{
diff --git a/Projects/HubX/HubX.Server/Services/UniqueKeyService.cs b/Projects/HubX/HubX.Server/Services/UniqueKeyService.cs
index 7b05f6c..36b7d2b 100644
--- a/Projects/HubX/HubX.Server/Services/UniqueKeyService.cs
+++ b/Projects/HubX/HubX.Server/Services/UniqueKeyService.cs
@@ -4,6 +4,7 @@ using HubX.Library.Http.Packet;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Client.Extensions.Msal;
using System;
+using System.Collections.Generic;
using System.Data;
using System.Xml;
using SystemX.Core.DB;
@@ -116,6 +117,33 @@ namespace HubX.Server.Services
return response;
}
+ public async Task> Request_SelectUniqueKeyAll(string guid = "")
+ {
+ List result = new List();
+ using (var scope = _scopeFactory.CreateScope())
+ {
+ var context = scope.ServiceProvider.GetRequiredService();
+ if (context != null)
+ {
+ try
+ {
+ using (var transaction = await context.CreateTransactionAsync(IsolationLevel.ReadUncommitted))
+ {
+ result = await context.TStorages.AsNoTracking().ToListAsync();
+ await context.CloseTransactionAsync(transaction);
+ }
+ }
+ catch (Exception e)
+ {
+ Log4net.WriteLine($"Select Unique Key Transaction Error::{guid}", LogType.Error);
+ Log4net.WriteLine(e);
+ }
+ }
+ }
+
+ return result;
+ }
+
public async Task Request_UpdateUniqueKey(Request_UpdateUniqueKey request, string guid = "")
{
Response_UpdateUniqueKy response = new Response_UpdateUniqueKy();
diff --git a/Projects/SystemX.Core/DBPatch/CreateAccountDB.bat b/Projects/SystemX.Core/DBPatch/CreateAccountDB.bat
new file mode 100644
index 0000000..d2c77f5
--- /dev/null
+++ b/Projects/SystemX.Core/DBPatch/CreateAccountDB.bat
@@ -0,0 +1,18 @@
+@echo off
+::log
+IF NOT EXIST .\logs mkdir logs
+
+::서버연결정보
+SET ServerIP=127.0.0.1
+SET ServerPort=1433
+
+::DB 정보
+SET UserID=SystemX
+SET Passwd=X
+SET DBName=AccountDB
+
+::Default DB
+@echo off
+CD .\sqlScripts\
+CALL _CreateScript.bat %ServerIP% %ServerPort% %UserID% %Passwd% %DBName%
+CALL _CreateScript.bat %ServerIP% %ServerPort% %UserID% %Passwd% %DBName%_DEV
\ No newline at end of file
diff --git a/Projects/SystemX.Core/DBPatch/UpdateAccountDB.bat b/Projects/SystemX.Core/DBPatch/UpdateAccountDB.bat
new file mode 100644
index 0000000..0d0b8db
--- /dev/null
+++ b/Projects/SystemX.Core/DBPatch/UpdateAccountDB.bat
@@ -0,0 +1,25 @@
+@echo off
+::log
+IF NOT EXIST .\logs mkdir logs
+
+::서버연결정보
+SET ServerIP=127.0.0.1
+SET ServerPort=1433
+
+::DB 정보
+SET UserID=SystemX
+SET Passwd=X
+SET DBName=AccountDB
+
+::Update script 정보
+SET Dacpac=.\dacpac\SystemX.DB.AccountDB.dacpac
+SET OUTPUT=SystemX.DB.AccountDB_Update.sql
+
+@echo off
+::generate update script
+CD .\sqlScripts\
+CALL _UpdateScriptGenerate.bat %ServerIP% %ServerPort% %UserID% %Passwd% %DBName% %Dacpac% %OUTPUT%
+
+::Default DB
+CALL _UpdateAccountDB.bat %ServerIP% %ServerPort% %UserID% %Passwd% %DBName%
+CALL _UpdateAccountDB.bat %ServerIP% %ServerPort% %UserID% %Passwd% %DBName%_DEV
\ No newline at end of file
diff --git a/Projects/SystemX.Core/DBPatch/_CreateSqlServerAccount_관리자권한으로실행.bat b/Projects/SystemX.Core/DBPatch/_CreateSqlServerAccount_관리자권한으로실행.bat
new file mode 100644
index 0000000..c6c8430
--- /dev/null
+++ b/Projects/SystemX.Core/DBPatch/_CreateSqlServerAccount_관리자권한으로실행.bat
@@ -0,0 +1,19 @@
+@echo Create Admin Account Start
+@echo off
+
+SET SqlCmdOption=-E -C
+sqlcmd %SqlCmdOption% -i %~dp0\sqlScripts\AdminAccount_Create.sql
+if errorlevel 1 goto errexit
+goto end
+:errexit
+echo DB Patch Fail
+goto end
+:end
+@echo on
+
+@echo Create Admin Account End
+
+net stop /y MSSQLSERVER
+net start /y MSSQLSERVER
+
+pause
\ No newline at end of file
diff --git a/Projects/SystemX.Core/DBPatch/sqlScripts/AdminAccount_Create.sql b/Projects/SystemX.Core/DBPatch/sqlScripts/AdminAccount_Create.sql
new file mode 100644
index 0000000..744268c
--- /dev/null
+++ b/Projects/SystemX.Core/DBPatch/sqlScripts/AdminAccount_Create.sql
@@ -0,0 +1,43 @@
+USE [master]
+GO
+
+CREATE LOGIN [SystemX] WITH PASSWORD=N'X', DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[English], CHECK_POLICY=ON
+GO
+
+ALTER LOGIN [SystemX] ENABLE
+GO
+
+ALTER SERVER ROLE [sysadmin] ADD MEMBER [SystemX]
+GO
+
+ALTER SERVER ROLE [securityadmin] ADD MEMBER [SystemX]
+GO
+
+ALTER SERVER ROLE [serveradmin] ADD MEMBER [SystemX]
+GO
+
+ALTER SERVER ROLE [setupadmin] ADD MEMBER [SystemX]
+GO
+
+
+
+USE [master]
+GO
+
+CREATE LOGIN [Alis] WITH PASSWORD=N'Kefico!@34', DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[English], CHECK_POLICY=ON
+GO
+
+ALTER LOGIN [Alis] ENABLE
+GO
+
+ALTER SERVER ROLE [sysadmin] ADD MEMBER [Alis]
+GO
+
+ALTER SERVER ROLE [securityadmin] ADD MEMBER [Alis]
+GO
+
+ALTER SERVER ROLE [serveradmin] ADD MEMBER [Alis]
+GO
+
+ALTER SERVER ROLE [setupadmin] ADD MEMBER [Alis]
+GO
\ No newline at end of file
diff --git a/Projects/SystemX.Core/DBPatch/sqlScripts/SystemX.DB.AccountDB_Create.sql b/Projects/SystemX.Core/DBPatch/sqlScripts/SystemX.DB.AccountDB_Create.sql
new file mode 100644
index 0000000..72848a5
--- /dev/null
+++ b/Projects/SystemX.Core/DBPatch/sqlScripts/SystemX.DB.AccountDB_Create.sql
@@ -0,0 +1,346 @@
+/*
+SystemX.DB.AccountDB의 배포 스크립트
+
+이 코드는 도구를 사용하여 생성되었습니다.
+파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
+변경 내용이 손실됩니다.
+*/
+
+GO
+SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;
+
+SET NUMERIC_ROUNDABORT OFF;
+
+
+GO
+/*
+:setvar DatabaseName "SystemX.DB.AccountDB"
+:setvar DefaultFilePrefix "SystemX.DB.AccountDB"
+:setvar DefaultDataPath ""
+:setvar DefaultLogPath ""
+*/
+
+GO
+:on error exit
+GO
+/*
+SQLCMD 모드가 지원되지 않으면 SQLCMD 모드를 검색하고 스크립트를 실행하지 않습니다.
+SQLCMD 모드를 설정한 후에 이 스크립트를 다시 사용하려면 다음을 실행합니다.
+SET NOEXEC OFF;
+*/
+:setvar __IsSqlCmdEnabled "True"
+GO
+IF N'$(__IsSqlCmdEnabled)' NOT LIKE N'True'
+ BEGIN
+ PRINT N'이 스크립트를 실행하려면 SQLCMD 모드를 사용하도록 설정해야 합니다.';
+ SET NOEXEC ON;
+ END
+
+
+GO
+USE [master];
+
+
+GO
+
+IF (DB_ID(N'$(DatabaseName)') IS NOT NULL)
+BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
+ DROP DATABASE [$(DatabaseName)];
+END
+
+GO
+PRINT N'$(DatabaseName) 데이터베이스를 만드는 중...'
+GO
+CREATE DATABASE [$(DatabaseName)] COLLATE Korean_Wansung_CI_AS
+GO
+USE [$(DatabaseName)];
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET ANSI_NULLS ON,
+ ANSI_PADDING ON,
+ ANSI_WARNINGS ON,
+ ARITHABORT ON,
+ CONCAT_NULL_YIELDS_NULL ON,
+ NUMERIC_ROUNDABORT OFF,
+ QUOTED_IDENTIFIER ON,
+ ANSI_NULL_DEFAULT ON,
+ CURSOR_DEFAULT LOCAL,
+ RECOVERY FULL,
+ CURSOR_CLOSE_ON_COMMIT OFF,
+ AUTO_CREATE_STATISTICS ON,
+ AUTO_SHRINK OFF,
+ AUTO_UPDATE_STATISTICS ON,
+ RECURSIVE_TRIGGERS OFF
+ WITH ROLLBACK IMMEDIATE;
+ END
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET ALLOW_SNAPSHOT_ISOLATION OFF;
+ END
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET READ_COMMITTED_SNAPSHOT OFF
+ WITH ROLLBACK IMMEDIATE;
+ END
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET AUTO_UPDATE_STATISTICS_ASYNC OFF,
+ PAGE_VERIFY NONE,
+ DATE_CORRELATION_OPTIMIZATION OFF,
+ DISABLE_BROKER,
+ PARAMETERIZATION SIMPLE,
+ SUPPLEMENTAL_LOGGING OFF
+ WITH ROLLBACK IMMEDIATE;
+ END
+
+
+GO
+IF IS_SRVROLEMEMBER(N'sysadmin') = 1
+ BEGIN
+ IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ EXECUTE sp_executesql N'ALTER DATABASE [$(DatabaseName)]
+ SET TRUSTWORTHY OFF,
+ DB_CHAINING OFF
+ WITH ROLLBACK IMMEDIATE';
+ END
+ END
+ELSE
+ BEGIN
+ PRINT N'데이터베이스 설정을 수정할 수 없습니다. 이러한 설정을 적용하려면 SysAdmin이어야 합니다.';
+ END
+
+
+GO
+IF IS_SRVROLEMEMBER(N'sysadmin') = 1
+ BEGIN
+ IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ EXECUTE sp_executesql N'ALTER DATABASE [$(DatabaseName)]
+ SET HONOR_BROKER_PRIORITY OFF
+ WITH ROLLBACK IMMEDIATE';
+ END
+ END
+ELSE
+ BEGIN
+ PRINT N'데이터베이스 설정을 수정할 수 없습니다. 이러한 설정을 적용하려면 SysAdmin이어야 합니다.';
+ END
+
+
+GO
+ALTER DATABASE [$(DatabaseName)]
+ SET TARGET_RECOVERY_TIME = 0 SECONDS
+ WITH ROLLBACK IMMEDIATE;
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET FILESTREAM(NON_TRANSACTED_ACCESS = OFF),
+ CONTAINMENT = NONE
+ WITH ROLLBACK IMMEDIATE;
+ END
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET AUTO_CREATE_STATISTICS ON(INCREMENTAL = OFF),
+ MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT = OFF,
+ DELAYED_DURABILITY = DISABLED
+ WITH ROLLBACK IMMEDIATE;
+ END
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET QUERY_STORE (QUERY_CAPTURE_MODE = ALL, DATA_FLUSH_INTERVAL_SECONDS = 900, INTERVAL_LENGTH_MINUTES = 60, MAX_PLANS_PER_QUERY = 200, CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 367), MAX_STORAGE_SIZE_MB = 100)
+ WITH ROLLBACK IMMEDIATE;
+ END
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET QUERY_STORE = OFF
+ WITH ROLLBACK IMMEDIATE;
+ END
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 0;
+ ALTER DATABASE SCOPED CONFIGURATION FOR SECONDARY SET MAXDOP = PRIMARY;
+ ALTER DATABASE SCOPED CONFIGURATION SET LEGACY_CARDINALITY_ESTIMATION = OFF;
+ ALTER DATABASE SCOPED CONFIGURATION FOR SECONDARY SET LEGACY_CARDINALITY_ESTIMATION = PRIMARY;
+ ALTER DATABASE SCOPED CONFIGURATION SET PARAMETER_SNIFFING = ON;
+ ALTER DATABASE SCOPED CONFIGURATION FOR SECONDARY SET PARAMETER_SNIFFING = PRIMARY;
+ ALTER DATABASE SCOPED CONFIGURATION SET QUERY_OPTIMIZER_HOTFIXES = OFF;
+ ALTER DATABASE SCOPED CONFIGURATION FOR SECONDARY SET QUERY_OPTIMIZER_HOTFIXES = PRIMARY;
+ END
+
+
+GO
+IF EXISTS (SELECT 1
+ FROM [master].[dbo].[sysdatabases]
+ WHERE [name] = N'$(DatabaseName)')
+ BEGIN
+ ALTER DATABASE [$(DatabaseName)]
+ SET TEMPORAL_HISTORY_RETENTION ON
+ WITH ROLLBACK IMMEDIATE;
+ END
+
+
+GO
+IF fulltextserviceproperty(N'IsFulltextInstalled') = 1
+ EXECUTE sp_fulltext_database 'enable';
+
+
+GO
+PRINT N'테이블 [dbo].[tRefreshToken]을(를) 만드는 중...';
+
+
+GO
+CREATE TABLE [dbo].[tRefreshToken] (
+ [cAuid] NVARCHAR (250) NOT NULL,
+ [cRefreshToken] NVARCHAR (1000) NOT NULL,
+ PRIMARY KEY CLUSTERED ([cAuid] ASC)
+);
+
+
+GO
+PRINT N'테이블 [dbo].[tRole]을(를) 만드는 중...';
+
+
+GO
+CREATE TABLE [dbo].[tRole] (
+ [cAuid] NVARCHAR (250) NOT NULL,
+ [cRoleID] TINYINT NOT NULL,
+ [cRoleName] NVARCHAR (20) NOT NULL,
+ PRIMARY KEY CLUSTERED ([cAuid] ASC)
+);
+
+
+GO
+PRINT N'테이블 [dbo].[tUser]을(를) 만드는 중...';
+
+
+GO
+CREATE TABLE [dbo].[tUser] (
+ [cUserID] NVARCHAR (50) NOT NULL,
+ [cAuid] NVARCHAR (250) NOT NULL,
+ [cPasswordHashed] NVARCHAR (250) NOT NULL,
+ [cState] TINYINT NOT NULL,
+ [cCreateDateTime] DATETIME2 (7) NOT NULL,
+ [cLastLoginDateTime] DATETIME2 (7) NULL,
+ PRIMARY KEY CLUSTERED ([cUserID] ASC)
+);
+
+
+GO
+/*
+배포 후 스크립트 템플릿
+--------------------------------------------------------------------------------------
+ 이 파일에는 빌드 스크립트에 추가될 SQL 문이 있습니다.
+ SQLCMD 구문을 사용하여 파일을 배포 후 스크립트에 포함합니다.
+ 예: :r .\myfile.sql
+ SQLCMD 구문을 사용하여 배포 후 스크립트의 변수를 참조합니다.
+ 예: :setvar TableName MyTable
+ SELECT * FROM [$(TableName)]
+--------------------------------------------------------------------------------------
+*/
+
+IF NOT EXISTS (SELECT 1 FROM tUser WHERE cUserID = 'Alis')
+BEGIN
+ INSERT INTO tUser (cUserID, cAuid, cPasswordHashed, cState, cCreateDateTime, cLastLoginDateTime)
+ VALUES ('Alis', 'SuperUserAlis' ,'oKLQCdunc2kT5aAVfK+POKwd8R3p8OZvs/NATwpg4gM=' ,1 ,GETDATE(), GETDATE());
+
+ INSERT INTO tRole(cAuid, cRoleID, cRoleName)
+ VALUES ('SuperUserAlis','20','SuperUser');
+END
+
+IF NOT EXISTS (SELECT 1 FROM tUser WHERE cUserID = 'SystemX')
+BEGIN
+ INSERT INTO tUser (cUserID, cAuid, cPasswordHashed, cState, cCreateDateTime, cLastLoginDateTime)
+ VALUES ('SystemX', 'SuperUserSystemX' ,'S2irOEf+2n1sYsH7y+6/o16rc1HtXnj03a3qXfZLgBU=' ,1 ,GETDATE(), GETDATE());
+
+ INSERT INTO tRole(cAuid, cRoleID, cRoleName)
+ VALUES ('SuperUserSystemX','20','SuperUser');
+END
+GO
+
+GO
+DECLARE @VarDecimalSupported AS BIT;
+
+SELECT @VarDecimalSupported = 0;
+
+IF ((ServerProperty(N'EngineEdition') = 3)
+ AND (((@@microsoftversion / power(2, 24) = 9)
+ AND (@@microsoftversion & 0xffff >= 3024))
+ OR ((@@microsoftversion / power(2, 24) = 10)
+ AND (@@microsoftversion & 0xffff >= 1600))))
+ SELECT @VarDecimalSupported = 1;
+
+IF (@VarDecimalSupported > 0)
+ BEGIN
+ EXECUTE sp_db_vardecimal_storage_format N'$(DatabaseName)', 'ON';
+ END
+
+
+GO
+ALTER DATABASE [$(DatabaseName)]
+ SET MULTI_USER
+ WITH ROLLBACK IMMEDIATE;
+
+
+GO
+PRINT N'업데이트가 완료되었습니다.';
+
+
+GO
diff --git a/Projects/SystemX.Core/DBPatch/sqlScripts/_CreateScript.bat b/Projects/SystemX.Core/DBPatch/sqlScripts/_CreateScript.bat
new file mode 100644
index 0000000..1627dc9
--- /dev/null
+++ b/Projects/SystemX.Core/DBPatch/sqlScripts/_CreateScript.bat
@@ -0,0 +1,18 @@
+@echo off
+
+SET ServerIP=%1
+SET ServerPort=%2
+SET UserID=%3
+SET Passwd=%4
+SET DBName=%5
+
+SET SqlCmdOption=-C -U %UserID% -P %Passwd% -S %ServerIP%,%ServerPort% -f 65001 -o ..\logs\%DBName%.log
+SET DatabaseName=%DBName%
+sqlcmd %SqlCmdOption% -i .\SystemX.DB.AccountDB_Create.sql
+if errorlevel 1 goto errexit
+goto end
+:errexit
+echo DB Patch Failed
+goto end
+:end
+@echo on
\ No newline at end of file
diff --git a/Projects/SystemX.Core/DBPatch/sqlScripts/_UpdateAccountDB.bat b/Projects/SystemX.Core/DBPatch/sqlScripts/_UpdateAccountDB.bat
new file mode 100644
index 0000000..d2df2be
--- /dev/null
+++ b/Projects/SystemX.Core/DBPatch/sqlScripts/_UpdateAccountDB.bat
@@ -0,0 +1,19 @@
+@echo off
+
+SET ServerIP=%1
+SET ServerPort=%2
+SET UserID=%3
+SET Passwd=%4
+SET DBName=%5
+
+SET SqlCmdOption=-U %UserID% -P %Passwd% -S %ServerIP%,%ServerPort% -d %DBName% -o ..\logs\%DBName%.log
+SET DatabaseName=%DBName%
+sqlcmd %SqlCmdOption% -i .\SystemX.DB.AccountDB_Update.sql
+
+if errorlevel 1 goto errexit
+goto end
+:errexit
+echo DB Patch Fail
+goto end
+:end
+@echo on
\ No newline at end of file
diff --git a/Projects/SystemX.Core/DBPatch/sqlScripts/_UpdateScriptGenerate.bat b/Projects/SystemX.Core/DBPatch/sqlScripts/_UpdateScriptGenerate.bat
new file mode 100644
index 0000000..56db9d9
--- /dev/null
+++ b/Projects/SystemX.Core/DBPatch/sqlScripts/_UpdateScriptGenerate.bat
@@ -0,0 +1,12 @@
+@echo off
+
+SET ServerIP=%1
+SET ServerPort=%2
+SET UserID=%3
+SET Passwd=%4
+SET DBName=%5
+SET Dacpac=%6
+SET OUTPUT=%7
+
+::create update sql file
+sqlpackage /Action:Script /SourceFile:%Dacpac% /TargetConnectionString:"server=%ServerIP%,%ServerPort%; user id=%UserID%; password=%Passwd%; database=%DBName%; TrustServerCertificate=true" /OutputPath:".\%OUTPUT%" /p:CommentOutSetVarDeclarations=True
\ No newline at end of file
diff --git a/Projects/SystemX.Core/DBPatch/sqlScripts/dacpac/SystemX.DB.AccountDB.dacpac b/Projects/SystemX.Core/DBPatch/sqlScripts/dacpac/SystemX.DB.AccountDB.dacpac
new file mode 100644
index 0000000..67cb16e
Binary files /dev/null and b/Projects/SystemX.Core/DBPatch/sqlScripts/dacpac/SystemX.DB.AccountDB.dacpac differ
diff --git a/Projects/SystemX.Core/SystemX.Core.sln b/Projects/SystemX.Core/SystemX.Core.sln
index 61b9793..15eec93 100644
--- a/Projects/SystemX.Core/SystemX.Core.sln
+++ b/Projects/SystemX.Core/SystemX.Core.sln
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemX.Core", "SystemX.Core\SystemX.Core.csproj", "{F057A1E8-F5FF-4241-BEEA-1A57E971F379}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemX.Core", "SystemX.Core\SystemX.Core.csproj", "{F057A1E8-F5FF-4241-BEEA-1A57E971F379}"
+EndProject
+Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "SystemX.DB.AccountDB", "SystemX.DB.AccountDB\SystemX.DB.AccountDB.sqlproj", "{B44C85FA-BD31-419F-8481-477E166A5753}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +17,12 @@ Global
{F057A1E8-F5FF-4241-BEEA-1A57E971F379}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F057A1E8-F5FF-4241-BEEA-1A57E971F379}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F057A1E8-F5FF-4241-BEEA-1A57E971F379}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B44C85FA-BD31-419F-8481-477E166A5753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B44C85FA-BD31-419F-8481-477E166A5753}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B44C85FA-BD31-419F-8481-477E166A5753}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {B44C85FA-BD31-419F-8481-477E166A5753}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B44C85FA-BD31-419F-8481-477E166A5753}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B44C85FA-BD31-419F-8481-477E166A5753}.Release|Any CPU.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Projects/SystemX.Core/SystemX.Core/Config/Model/Auth.cs b/Projects/SystemX.Core/SystemX.Core/Config/Model/Auth.cs
new file mode 100644
index 0000000..bc5f7ab
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Config/Model/Auth.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.Config.Model
+{
+ public class Auth
+ {
+ //jwt common
+ [JsonPropertyName("issuer")]
+ public string? issuer { get; set; }
+
+ [JsonPropertyName("audience")]
+ public string? audience { get; set; }
+
+ //access token
+ [JsonPropertyName("accessTokenSecret")]
+ public string? accessTokenSecret { get; set; }
+
+ [JsonPropertyName("accessTokenExpires")]
+ public uint? accessTokenExpires { get; set; }
+
+ //refresh token
+ [JsonPropertyName("refreshTokenSecret")]
+ public string? refreshTokenSecret { get; set; }
+
+ [JsonPropertyName("refreshTokenExpires")]
+ public uint? refreshTokenExpires { get; set; }
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Context/AccountDbContext.cs b/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Context/AccountDbContext.cs
new file mode 100644
index 0000000..f330eda
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Context/AccountDbContext.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using SystemX.Core.DB.DBContext.AccountDB.Tables;
+
+namespace SystemX.Core.DB.DBContext.AccountDB.Context;
+
+public partial class AccountDbContext : DbContext
+{
+ public AccountDbContext()
+ {
+ }
+
+ public AccountDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ public virtual DbSet TRefreshTokens { get; set; }
+
+ public virtual DbSet TRoles { get; set; }
+
+ public virtual DbSet TUsers { get; set; }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
+ => optionsBuilder.UseSqlServer("server=127.0.0.1; user id=SystemX; password=X; database=AccountDB; TrustServerCertificate=true;");
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.CAuid).HasName("PK__tRefresh__FBF0855465EB95AB");
+
+ entity.ToTable("tRefreshToken");
+
+ entity.Property(e => e.CAuid)
+ .HasMaxLength(250)
+ .HasColumnName("cAuid");
+ entity.Property(e => e.CRefreshToken)
+ .HasMaxLength(1000)
+ .HasColumnName("cRefreshToken");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.CAuid).HasName("PK__tRole__FBF085540BB887D7");
+
+ entity.ToTable("tRole");
+
+ entity.Property(e => e.CAuid)
+ .HasMaxLength(250)
+ .HasColumnName("cAuid");
+ entity.Property(e => e.CRoleId).HasColumnName("cRoleID");
+ entity.Property(e => e.CRoleName)
+ .HasMaxLength(20)
+ .HasColumnName("cRoleName");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.CUserId).HasName("PK__tUser__A75DC19A721265FF");
+
+ entity.ToTable("tUser");
+
+ entity.Property(e => e.CUserId)
+ .HasMaxLength(50)
+ .HasColumnName("cUserID");
+ entity.Property(e => e.CAuid)
+ .HasMaxLength(250)
+ .HasColumnName("cAuid");
+ entity.Property(e => e.CCreateDateTime).HasColumnName("cCreateDateTime");
+ entity.Property(e => e.CLastLoginDateTime).HasColumnName("cLastLoginDateTime");
+ entity.Property(e => e.CPasswordHashed)
+ .HasMaxLength(250)
+ .HasColumnName("cPasswordHashed");
+ entity.Property(e => e.CState).HasColumnName("cState");
+ });
+
+ OnModelCreatingPartial(modelBuilder);
+ }
+
+ partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Tables/TRefreshToken.cs b/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Tables/TRefreshToken.cs
new file mode 100644
index 0000000..c2578dc
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Tables/TRefreshToken.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+
+namespace SystemX.Core.DB.DBContext.AccountDB.Tables;
+
+public partial class TRefreshToken
+{
+ public string CAuid { get; set; } = null!;
+
+ public string CRefreshToken { get; set; } = null!;
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Tables/TRole.cs b/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Tables/TRole.cs
new file mode 100644
index 0000000..5404196
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Tables/TRole.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+
+namespace SystemX.Core.DB.DBContext.AccountDB.Tables;
+
+public partial class TRole
+{
+ public string CAuid { get; set; } = null!;
+
+ public byte CRoleId { get; set; }
+
+ public string CRoleName { get; set; } = null!;
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Tables/TUser.cs b/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Tables/TUser.cs
new file mode 100644
index 0000000..4a10d36
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/DB/DBContext/AccountDB/Tables/TUser.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+
+namespace SystemX.Core.DB.DBContext.AccountDB.Tables;
+
+public partial class TUser
+{
+ public string CUserId { get; set; } = null!;
+
+ public string CAuid { get; set; } = null!;
+
+ public string CPasswordHashed { get; set; } = null!;
+
+ public byte CState { get; set; }
+
+ public DateTime CCreateDateTime { get; set; }
+
+ public DateTime? CLastLoginDateTime { get; set; }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/ERROR_CODE.cs b/Projects/SystemX.Core/SystemX.Core/ERROR_CODE.cs
new file mode 100644
index 0000000..e58b1e6
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/ERROR_CODE.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SystemX.Core
+{
+ public enum ERROR_CODE
+ {
+ //기본 에러
+ EC_NONE = 0,
+ EC_OK = 1,
+
+ //DB 관련 에러
+ EC_DEFAULT_ERROR = 1000,
+ EC_DATABASE_ERROR = 1001,
+
+ //유저 관련 에러
+ EC_USER_REGISTER_FAILED = 2000,
+ EC_USER_REGISTER_EXIST = 2001,
+ EC_USER_REGISTER_CONFIRM_PASSWORD_ERROR = 2002,
+
+ EC_USER_LOGIN_FAILED = 2100,
+ EC_USER_LOGIN_NOT_EXIST = 2101,
+ EC_USER_LOGIN_INVALID_PASSWORD = 2102,
+ EC_USER_LOGIN_INAVTIVE = 2103,
+ EC_USER_LOGIN_BLOCKED = 2104,
+
+ EC_USER_LOGOUT_FAILED = 2200
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/Log4net/Log4net.cs b/Projects/SystemX.Core/SystemX.Core/Log4net/Log4net.cs
index 30c0754..adef78f 100644
--- a/Projects/SystemX.Core/SystemX.Core/Log4net/Log4net.cs
+++ b/Projects/SystemX.Core/SystemX.Core/Log4net/Log4net.cs
@@ -57,6 +57,8 @@ public static class Log4net
public static bool IsConfigLoad { get; set; } = false;
+ public static string Log4netConfigPath { get; } = @"../../Config/log4net.config";
+
//로그 사용여부
public static bool IsDebugEnabled { get; set; } = true;
public static bool IsDBEnabled { get; set; } = true;
@@ -70,7 +72,7 @@ public static class Log4net
static Log4net()
{
- string log4netConfigPath = @"../Config/log4net.config";
+ string log4netConfigPath = Log4netConfigPath;
if (File.Exists(log4netConfigPath) == true)
{
@@ -82,7 +84,7 @@ public static class Log4net
IsConfigLoad = OpenConfig(log4netConfigPath);
}
-
+
private static bool OpenConfig(string path)
{
bool result = true;
diff --git a/Projects/SystemX.Core/SystemX.Core/Model/Auth/LoginModel.cs b/Projects/SystemX.Core/SystemX.Core/Model/Auth/LoginModel.cs
new file mode 100644
index 0000000..997084b
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Model/Auth/LoginModel.cs
@@ -0,0 +1,34 @@
+using log4net.Core;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.Model.Auth
+{
+ //로그인 요청 모델
+ public class LoginModel
+ {
+ [Required]
+ public string? UserID { get; set; }
+
+ [Required]
+ public string? Password { get; set; }
+ }
+
+ //로그인 응답 모델
+ public class LoginResponseModel
+ {
+ public string? UserID { get; set; }
+ public UserRole Role { get; set; }
+ public string? RoleName { get; set; }
+
+ public string? AccessToken { get; set; }
+ public long AccessTokenExpired { get; set; }
+ public string? RefreshToken { get; set; }
+
+ public ERROR_CODE? EC { get; set; }
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/Model/Auth/LogoutModel.cs b/Projects/SystemX.Core/SystemX.Core/Model/Auth/LogoutModel.cs
new file mode 100644
index 0000000..bf8f7a1
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Model/Auth/LogoutModel.cs
@@ -0,0 +1,23 @@
+using log4net.Core;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.Model.Auth
+{
+ public class LogoutModel
+ {
+ [Required]
+ public string? UserID { get; set; }
+ }
+
+ public class LogoutResponseModel
+ {
+ public string? UserID { get; set; }
+
+ public ERROR_CODE? EC { get; set; }
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/Model/Auth/RegisterModel.cs b/Projects/SystemX.Core/SystemX.Core/Model/Auth/RegisterModel.cs
new file mode 100644
index 0000000..67085f8
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Model/Auth/RegisterModel.cs
@@ -0,0 +1,35 @@
+using log4net.Core;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.Model.Auth
+{
+ //유저 등록 모델
+ public class RegisterModel
+ {
+ [Required]
+ public string UserID { get; set; } = string.Empty;
+
+ [Required]
+ public string Password { get; set; } = string.Empty;
+
+ [Required]
+ public string PasswordConfirm { get; set; } = string.Empty;
+
+ [Required]
+ public UserRole Role { get; set; } = UserRole.User;
+ }
+
+ public class RegisterResponseModel
+ {
+ public string? UserID { get; set; }
+ public UserRole Role { get; set; }
+ public string? RoleName { get; set; }
+
+ public ERROR_CODE? EC { get; set; } = ERROR_CODE.EC_USER_REGISTER_FAILED;
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/Model/Auth/UserModel.cs b/Projects/SystemX.Core/SystemX.Core/Model/Auth/UserModel.cs
new file mode 100644
index 0000000..660f847
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Model/Auth/UserModel.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.Model.Auth
+{
+ public class UserModel
+ {
+ //public TUser? TUser { get; set; }
+ //public TRole? TRole { get; set; }
+ }
+}
diff --git a/Projects/VPKI/VPKI/VPKI.Library/Enums/UserRole.cs b/Projects/SystemX.Core/SystemX.Core/Model/Auth/UserRole.cs
similarity index 89%
rename from Projects/VPKI/VPKI/VPKI.Library/Enums/UserRole.cs
rename to Projects/SystemX.Core/SystemX.Core/Model/Auth/UserRole.cs
index b5ca981..07840ae 100644
--- a/Projects/VPKI/VPKI/VPKI.Library/Enums/UserRole.cs
+++ b/Projects/SystemX.Core/SystemX.Core/Model/Auth/UserRole.cs
@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace VPKI.Library.Enums
+namespace SystemX.Core.Model.Auth
{
//User 권한
public enum UserRole : byte
diff --git a/Projects/VPKI/VPKI/VPKI.Library/Enums/UserState.cs b/Projects/SystemX.Core/SystemX.Core/Model/Auth/UserState.cs
similarity index 74%
rename from Projects/VPKI/VPKI/VPKI.Library/Enums/UserState.cs
rename to Projects/SystemX.Core/SystemX.Core/Model/Auth/UserState.cs
index 0d48b84..0b15639 100644
--- a/Projects/VPKI/VPKI/VPKI.Library/Enums/UserState.cs
+++ b/Projects/SystemX.Core/SystemX.Core/Model/Auth/UserState.cs
@@ -4,9 +4,9 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace VPKI.Library.Enums
+namespace SystemX.Core.Model.Auth
{
- public enum UserState: byte
+ public enum UserState : byte
{
Inactive = 0,
Active = 1,
diff --git a/Projects/SystemX.Core/SystemX.Core/SystemX.Core.csproj b/Projects/SystemX.Core/SystemX.Core/SystemX.Core.csproj
index 17016a0..3ffe46e 100644
--- a/Projects/SystemX.Core/SystemX.Core/SystemX.Core.csproj
+++ b/Projects/SystemX.Core/SystemX.Core/SystemX.Core.csproj
@@ -9,18 +9,26 @@
9999
+ False
9999
+ False
-
-
-
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/Projects/SystemX.Core/SystemX.DB.AccountDB/SystemX.DB.AccountDB.sqlproj b/Projects/SystemX.Core/SystemX.DB.AccountDB/SystemX.DB.AccountDB.sqlproj
new file mode 100644
index 0000000..d94a9b7
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.DB.AccountDB/SystemX.DB.AccountDB.sqlproj
@@ -0,0 +1,80 @@
+
+
+
+ Debug
+ AnyCPU
+ SystemX.DB.AccountDB
+ 2.0
+ 4.1
+ {b44c85fa-bd31-419f-8481-477e166a5753}
+ Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider
+ Database
+
+
+ SystemX.DB.AccountDB
+ SystemX.DB.AccountDB
+ 1042,CI
+ BySchemaAndSchemaType
+ True
+ v4.7.2
+ CS
+ Properties
+ False
+ True
+ True
+ True
+ Korean_Wansung_CI_AS
+
+
+ bin\Release\
+ $(MSBuildProjectName).sql
+ False
+ pdbonly
+ true
+ false
+ true
+ prompt
+ 4
+
+
+ bin\Debug\
+ $(MSBuildProjectName).sql
+ true
+ true
+ full
+ false
+ true
+ true
+ prompt
+ 4
+
+
+ 11.0
+
+ True
+ 11.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xcopy /y $(ProjectDir)$(OutputPath)$(TargetName)_Create.sql $(SolutionDir)DBPatch\sqlScripts\
+
+xcopy /y $(ProjectDir)$(OutputPath)$(TargetName).dacpac $(SolutionDir)DBPatch\sqlScripts\dacpac\
+
+
\ No newline at end of file
diff --git a/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Scripts/scriptAfterBuild.sql b/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Scripts/scriptAfterBuild.sql
new file mode 100644
index 0000000..b57fab3
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Scripts/scriptAfterBuild.sql
@@ -0,0 +1,29 @@
+/*
+배포 후 스크립트 템플릿
+--------------------------------------------------------------------------------------
+ 이 파일에는 빌드 스크립트에 추가될 SQL 문이 있습니다.
+ SQLCMD 구문을 사용하여 파일을 배포 후 스크립트에 포함합니다.
+ 예: :r .\myfile.sql
+ SQLCMD 구문을 사용하여 배포 후 스크립트의 변수를 참조합니다.
+ 예: :setvar TableName MyTable
+ SELECT * FROM [$(TableName)]
+--------------------------------------------------------------------------------------
+*/
+
+IF NOT EXISTS (SELECT 1 FROM tUser WHERE cUserID = 'Alis')
+BEGIN
+ INSERT INTO tUser (cUserID, cAuid, cPasswordHashed, cState, cCreateDateTime, cLastLoginDateTime)
+ VALUES ('Alis', 'SuperUserAlis' ,'oKLQCdunc2kT5aAVfK+POKwd8R3p8OZvs/NATwpg4gM=' ,1 ,GETDATE(), GETDATE());
+
+ INSERT INTO tRole(cAuid, cRoleID, cRoleName)
+ VALUES ('SuperUserAlis','20','SuperUser');
+END
+
+IF NOT EXISTS (SELECT 1 FROM tUser WHERE cUserID = 'SystemX')
+BEGIN
+ INSERT INTO tUser (cUserID, cAuid, cPasswordHashed, cState, cCreateDateTime, cLastLoginDateTime)
+ VALUES ('SystemX', 'SuperUserSystemX' ,'S2irOEf+2n1sYsH7y+6/o16rc1HtXnj03a3qXfZLgBU=' ,1 ,GETDATE(), GETDATE());
+
+ INSERT INTO tRole(cAuid, cRoleID, cRoleName)
+ VALUES ('SuperUserSystemX','20','SuperUser');
+END
\ No newline at end of file
diff --git a/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Tables/tRefreshToken.sql b/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Tables/tRefreshToken.sql
new file mode 100644
index 0000000..0ec9fd9
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Tables/tRefreshToken.sql
@@ -0,0 +1,5 @@
+CREATE TABLE [dbo].[tRefreshToken]
+(
+ [cAuid] NVARCHAR(250) NOT NULL PRIMARY KEY,
+ [cRefreshToken] NVARCHAR(1000) NOT NULL
+)
diff --git a/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Tables/tRole.sql b/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Tables/tRole.sql
new file mode 100644
index 0000000..5a49a82
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Tables/tRole.sql
@@ -0,0 +1,6 @@
+CREATE TABLE [dbo].[tRole]
+(
+ [cAuid] NVARCHAR(250) NOT NULL PRIMARY KEY,
+ [cRoleID] TINYINT NOT NULL,
+ [cRoleName] NVARCHAR(20) NOT NULL
+)
diff --git a/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Tables/tUser.sql b/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Tables/tUser.sql
new file mode 100644
index 0000000..a7e6a64
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.DB.AccountDB/dbo/Tables/tUser.sql
@@ -0,0 +1,10 @@
+CREATE TABLE [dbo].[tUser]
+(
+ [cUserID] NVARCHAR(50) NOT NULL,
+ [cAuid] NVARCHAR(250) NOT NULL ,
+ [cPasswordHashed] NVARCHAR(250) NOT NULL,
+ [cState] tinyint NOT NULL,
+ [cCreateDateTime] DATETIME2 NOT NULL,
+ [cLastLoginDateTime] DATETIME2 NULL,
+ PRIMARY KEY ([cUserID])
+)
diff --git a/Projects/Tools/Tools_Scaffold_AccountDB.bat b/Projects/Tools/Tools_Scaffold_AccountDB.bat
new file mode 100644
index 0000000..425d4f8
--- /dev/null
+++ b/Projects/Tools/Tools_Scaffold_AccountDB.bat
@@ -0,0 +1,5 @@
+::AccountDB
+cd ../SystemX.Core/SystemX.Core
+
+::WebApi
+dotnet ef dbcontext scaffold "server=127.0.0.1; user id=SystemX; password=X; database=AccountDB; TrustServerCertificate=true;" Microsoft.EntityFrameworkCore.SqlServer --namespace SystemX.Core.DBContext --context-dir DBContext\AccountDB\Context --output-dir DBContext\AccountDB\Tables -f
\ No newline at end of file
diff --git a/Projects/WebApi/AuthApi/AuthApi.csproj b/Projects/WebApi/AuthApi/AuthApi.csproj
index 9daa180..e65ec2b 100644
--- a/Projects/WebApi/AuthApi/AuthApi.csproj
+++ b/Projects/WebApi/AuthApi/AuthApi.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
@@ -6,8 +6,32 @@
enable
+
+ True
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+ ..\..\DLL\SystemX.Core.dll
+
+
+
+
+
+
+
diff --git a/Projects/WebApi/AuthApi/Controllers/AuthController.cs b/Projects/WebApi/AuthApi/Controllers/AuthController.cs
new file mode 100644
index 0000000..83c9889
--- /dev/null
+++ b/Projects/WebApi/AuthApi/Controllers/AuthController.cs
@@ -0,0 +1,146 @@
+using AuthApi.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using SystemX.Core;
+using SystemX.Core.Model.Auth;
+
+namespace AuthApi.Controllers
+{
+ [Tags("Auth")]
+ [Route("api/auth")]
+ [ApiController]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ public class AuthController : CommonController
+ {
+ private readonly AuthService _authService;
+
+ public AuthController(IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor,
+ AuthService authService)
+ : base(serviceProvider, httpContextAccessor)
+ {
+ _authService = authService;
+ }
+
+ [HttpGet("/health")]
+ public async Task Health()
+ {
+ await Task.CompletedTask;
+ return Results.Ok("Healthy");
+ }
+
+ [HttpPost("regisger")]
+ public async Task Register([FromBody] RegisterModel request)
+ {
+ // Log4net.WriteLine(GetRequestLog(request).LogModelToString("Request Auth"), LogType.CONTROLLER);
+
+ RegisterResponseModel response = new RegisterResponseModel();
+
+ if (request?.UserID != null && request?.Password != null)
+ {
+ response = await _authService.CreateUser(request);
+ }
+
+ // Log4net.WriteLine(GetResponseLog(response).LogModelToString("Response Auth"), LogType.CONTROLLER);
+
+ return Results.Ok(response);
+ }
+
+ [HttpPost("login")]
+ public async Task Login([FromBody] LoginModel request)
+ {
+ // Log4net.WriteLine(GetRequestLog(request).LogModelToString("Request Auth"), LogType.CONTROLLER);
+
+ LoginResponseModel response = new LoginResponseModel();
+ response.UserID = request.UserID;
+ response.EC = ERROR_CODE.EC_USER_LOGIN_FAILED;
+
+ if (request.UserID != null && request.Password != null)
+ {
+ response = await _authService.SelectUser(request);
+
+ if (response.EC == ERROR_CODE.EC_OK)
+ {
+ double convertExpires = Convert.ToDouble(_configService?.GetConfig()?.Auth?.accessTokenExpires);
+
+ response.AccessToken = GenerateJwtToken(response);
+ response.AccessTokenExpired = DateTime.UtcNow.AddMinutes(convertExpires).ToUnixTime();
+
+ response.RefreshToken = GenerateJwtToken(response, true);
+ }
+
+ await _authService.UpdateLoginInfo(request, response.RefreshToken);
+ }
+
+ // Log4net.WriteLine(GetResponseLog(response).LogModelToString("Response Auth"), LogType.CONTROLLER);
+
+ return Results.Ok(response);
+ }
+
+ [HttpPost("logout")]
+ public async Task Logout([FromBody] LogoutModel request)
+ {
+ // Log4net.WriteLine(GetRequestLog(request).LogModelToString("Request Auth"), LogType.CONTROLLER);
+
+ var response = _authService.LogoutUser(request);
+ await Task.CompletedTask;
+
+ // Log4net.WriteLine(GetResponseLog(response).LogModelToString("Response Auth"), LogType.CONTROLLER);
+
+ return Results.Ok(response);
+ }
+
+ [Authorize]
+ [HttpPost("validate")]
+ public ActionResult Validate([FromBody] string authToken)
+ {
+ return "";
+ }
+
+ private TokenValidationParameters GetValidationParameters()
+ {
+ return new TokenValidationParameters()
+ {
+ ValidateLifetime = true,
+ ValidateAudience = true,
+ ValidateIssuer = true,
+ ValidIssuer = $"{_configService?.GetConfig()?.Auth?.issuer}",
+ ValidAudience = $"{_configService?.GetConfig()?.Auth?.issuer}",
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes($"{_configService?.GetConfig()?.Auth?.accessTokenSecret}"))
+ };
+ }
+
+ private string GenerateJwtToken(LoginResponseModel loginResponseModel, bool isRefreshToken = false)
+ {
+ var claims = new[]
+ {
+ new Claim(ClaimTypes.Name, $"{loginResponseModel.UserID}"),
+ new Claim(ClaimTypes.Role, $"{loginResponseModel.RoleName}"),
+ };
+
+ string secret = $"{_configService?.GetConfig()?.Auth?.accessTokenSecret}";
+ double convertExpires = Convert.ToDouble(_configService?.GetConfig()?.Auth?.accessTokenExpires);
+ if (isRefreshToken == true)
+ {
+ secret = $"{_configService?.GetConfig()?.Auth?.refreshTokenSecret}";
+ convertExpires = Convert.ToDouble(_configService?.GetConfig()?.Auth?.refreshTokenExpires);
+ }
+
+ var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
+ var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
+
+ var token = new JwtSecurityToken(
+ issuer: $"{_configService?.GetConfig()?.Auth?.issuer}",
+ audience: $"{_configService?.GetConfig()?.Auth?.audience}",
+ claims: claims,
+ expires: DateTime.UtcNow.AddMinutes(convertExpires),
+ signingCredentials: creds
+ );
+
+ return new JwtSecurityTokenHandler().WriteToken(token);
+ }
+ }
+}
diff --git a/Projects/WebApi/AuthApi/Controllers/CommonController.cs b/Projects/WebApi/AuthApi/Controllers/CommonController.cs
new file mode 100644
index 0000000..ef12193
--- /dev/null
+++ b/Projects/WebApi/AuthApi/Controllers/CommonController.cs
@@ -0,0 +1,59 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Runtime.CompilerServices;
+using SystemX.Core.Services;
+using WebApi.Library.Config;
+
+namespace AuthApi.Controllers
+{
+ public class CommonController : ControllerBase
+ {
+ public readonly IServiceProvider _serviceProvider;
+ public readonly IHttpContextAccessor _httpContextAccessor;
+
+ public readonly ConfigService? _configService;
+
+ protected static Guid guid { get; private set; } = Guid.NewGuid();
+
+ public CommonController(IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor)
+ {
+ //provider
+ _serviceProvider = serviceProvider;
+ _httpContextAccessor = httpContextAccessor;
+
+ //service
+ _configService = _serviceProvider.GetService>();
+ }
+
+ ///
+ /// Request 클라이언트 IP
+ ///
+ protected virtual string? GetClientIP()
+ {
+ return _httpContextAccessor?.HttpContext?.Connection?.RemoteIpAddress?.ToString();
+ }
+
+ ///
+ /// Request 클라이언트 Url
+ ///
+ protected virtual string? GetRequestUrl()
+ {
+ return _httpContextAccessor?.HttpContext?.Request?.Path;
+ }
+
+ ///
+ /// Request 클라이언트 method: [GET] or [POST]
+ ///
+ protected virtual string? GetRequestMethod()
+ {
+ return _httpContextAccessor?.HttpContext?.Request?.Method;
+ }
+
+ ///
+ /// 현재 Action(함수) 이름 가져오기
+ ///
+ protected virtual string GetMethodName([CallerMemberName] string callerMemberName = "")
+ {
+ return callerMemberName;
+ }
+ }
+}
diff --git a/Projects/WebApi/AuthApi/Controllers/WeatherForecastController.cs b/Projects/WebApi/AuthApi/Controllers/WeatherForecastController.cs
deleted file mode 100644
index a18781e..0000000
--- a/Projects/WebApi/AuthApi/Controllers/WeatherForecastController.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-
-namespace AuthApi.Controllers
-{
- [ApiController]
- [Route("[controller]")]
- public class WeatherForecastController : ControllerBase
- {
- private static readonly string[] Summaries = new[]
- {
- "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
- };
-
- private readonly ILogger _logger;
-
- public WeatherForecastController(ILogger logger)
- {
- _logger = logger;
- }
-
- [HttpGet(Name = "GetWeatherForecast")]
- public IEnumerable Get()
- {
- return Enumerable.Range(1, 5).Select(index => new WeatherForecast
- {
- Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
- TemperatureC = Random.Shared.Next(-20, 55),
- Summary = Summaries[Random.Shared.Next(Summaries.Length)]
- })
- .ToArray();
- }
- }
-}
diff --git a/Projects/WebApi/AuthApi/Program.cs b/Projects/WebApi/AuthApi/Program.cs
index 48863a6..5135652 100644
--- a/Projects/WebApi/AuthApi/Program.cs
+++ b/Projects/WebApi/AuthApi/Program.cs
@@ -1,25 +1,118 @@
+using AuthApi.Services;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.IdentityModel.Tokens;
+using System.ComponentModel;
+using System.Text;
+using SystemX.Core.Services;
+using WebApi.Library.Config;
+
+string configDir = @"../../Config";
+string configFileName = "WebApi.AuthApi.Config.json";
+
+//raed log4net configs
+if (Log4net.IsConfigLoad == true)
+{
+ Log4net.WriteLine("Log4net Init Success");
+ Log4net.AutoRemoveLog();
+}
+else
+{
+ Console.WriteLine("Log4net Init Failed");
+ return;
+}
+
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
+//singleton
+builder.Services.AddSingleton>();
+builder.Services.AddScoped();
+
+//scoped
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
+//config preload, auth set
+ConfigService preloadConfig = new ConfigService();
+if (preloadConfig.OpenConfig($@"{configDir}/{configFileName}") == true)
+{
+ var config = preloadConfig.GetConfig();
+
+ //auth
+ builder.Services
+ .AddAuthentication(option =>
+ {
+ option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(options =>
+ {
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ ClockSkew = TimeSpan.Zero,
+ ValidIssuer = $"{config?.Auth?.issuer}",
+ ValidAudience = $"{config?.Auth?.audience}",
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes($"{config?.Auth?.accessTokenSecret}"))
+ };
+ });
+}
+else
+{
+ Log4net.WriteLine("Config Preload Load Error.", LogType.Error);
+ return;
+}
+
var app = builder.Build();
+//read api config and set
+string serverUrl = string.Empty;
+var configService = app.Services.GetService>();
+bool isIIS = false;
+
+if (configService?.OpenConfig($@"{configDir}/{configFileName}") == true)
+{
+ Log4net.WriteLine("WebApi Config Success.");
+ var apiConfig = ConfigService.Config;
+ if (apiConfig != null)
+ {
+ serverUrl = $"{apiConfig?.Server?.Address}:{apiConfig?.Server?.Port}";
+ isIIS = apiConfig!.Server.IIS;
+ }
+}
+else
+{
+ Log4net.WriteLine("WebApi Config Error.");
+ return;
+}
+
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
+ Log4net.WriteLine($"IsDevelopment:{app.Environment.IsDevelopment()}");
+ Log4net.WriteLine($"Swagger Url: {serverUrl}/swagger");
app.UseSwagger();
app.UseSwaggerUI();
}
+app.UseAuthentication();
app.UseHttpsRedirection();
-
app.UseAuthorization();
app.MapControllers();
-app.Run();
+if (isIIS == true)
+{
+ app.Run();
+}
+else
+{
+ Log4net.WriteLine($"Operation Url: {serverUrl}");
+ app.Run($"{serverUrl}");
+}
diff --git a/Projects/WebApi/AuthApi/Services/AuthService.cs b/Projects/WebApi/AuthApi/Services/AuthService.cs
new file mode 100644
index 0000000..acced8d
--- /dev/null
+++ b/Projects/WebApi/AuthApi/Services/AuthService.cs
@@ -0,0 +1,263 @@
+using SystemX.Core.Model.Auth;
+using SystemX.Core.Services;
+using SystemX.Core;
+using WebApi.Library.Config;
+using SystemX.Core.Config.Model;
+using System.Data;
+using SystemX.Core.DB;
+using Microsoft.EntityFrameworkCore;
+using SystemX.Core.DB.DBContext.AccountDB.Context;
+using SystemX.Core.DB.DBContext.AccountDB.Tables;
+
+namespace AuthApi.Services
+{
+ public class AuthService
+ {
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IServiceScopeFactory _scopeFactory;
+ private readonly ConfigService? _configService;
+
+ private readonly DataBase? _accountDB;
+
+ private static List Session = new List();
+
+ public AuthService(IServiceProvider serviceProvider, IServiceScopeFactory scopeFactory, ConfigService configSerice)
+ {
+ _serviceProvider = serviceProvider;
+ _configService = configSerice;
+ _scopeFactory = scopeFactory;
+ _accountDB = _configService?.GetConfig()?.DataBase?.Find(x => x.DBContext == "VpkiAccountDbContext");
+ }
+
+ ///
+ /// create new user
+ ///
+ public async Task CreateUser(RegisterModel registerModel)
+ {
+ //response
+ RegisterResponseModel response = new RegisterResponseModel();
+ response.EC = ERROR_CODE.EC_USER_REGISTER_FAILED;
+ response.UserID = registerModel.UserID;
+ response.Role = registerModel.Role;
+ response.RoleName = registerModel.Role.ToString();
+
+ //context
+ using (var scope = _scopeFactory.CreateScope())
+ {
+ var context = scope.ServiceProvider.GetRequiredService();
+ if (context is not null)
+ {
+ var user = await context.TUsers.AsNoTracking().Where(x => x.CUserId.ToLower() == registerModel.UserID.ToLower()).ToListAsync();
+ if (user?.Count <= 0)
+ {
+ string auid = Guid.NewGuid().ToString();
+ //user
+ TUser newUser = new TUser
+ {
+ CAuid = auid,
+ CUserId = registerModel.UserID,
+ CPasswordHashed = registerModel.Password,
+ CCreateDateTime = DateTime.Now,
+ CLastLoginDateTime = new DateTime()
+ };
+ //role
+ TRole newUserRole = new TRole
+ {
+ CAuid = auid,
+ CRoleId = Convert.ToByte(registerModel.Role),
+ CRoleName = registerModel.Role.ToString()
+ };
+
+ using (var transaction = await context.CreateTransactionAsync())
+ {
+ await context.AddAsync(newUser);
+ await context.AddAsync(newUserRole);
+
+ var result = await context.CloseTransactionAsync(transaction);
+ if (result == true)
+ {
+ response.EC = ERROR_CODE.EC_OK;
+ }
+ }
+ }
+ }
+ }
+
+ return response;
+ }
+
+ ///
+ /// select user(login)
+ ///
+ public async Task SelectUser(LoginModel loginModel)
+ {
+ //response
+ LoginResponseModel response = new LoginResponseModel();
+ response.EC = ERROR_CODE.EC_USER_LOGIN_FAILED;
+ response.UserID = loginModel.UserID;
+
+ //var session = Session.Find(x => x.UserID?.ToLower() == loginModel.UserID?.ToLower());
+ //if (session?.AccessTokenExpired < DateTime.Now.ToUnixTime())
+ //{
+ // Session.Remove(session);
+ //}
+
+ //기존 로그인 체크
+ // if (Session.Exists(x => x.UserID == $"{loginModel.UserID?.ToLower()}") == false)
+ {
+ if (loginModel != null)
+ {
+ //context
+ using (var scope = _scopeFactory.CreateScope())
+ {
+ var context = scope.ServiceProvider.GetRequiredService();
+ if (context is not null)
+ {
+ try
+ {
+ using (var transaction = await context.CreateTransactionAsync(IsolationLevel.ReadUncommitted))
+ {
+ //select user
+ var selectUser = await context.TUsers.AsNoTracking().FirstOrDefaultAsync(x => x.CUserId.ToLower() == loginModel!.UserID!.ToLower());
+ if (selectUser is not null)
+ {
+ if (selectUser.CPasswordHashed == loginModel?.Password)
+ {
+ //select role
+ var selectRole = await context.TRoles.FindAsync(selectUser.CAuid);
+ if (selectRole != null)
+ {
+ response.Role = (UserRole)Enum.Parse(typeof(UserRole), selectRole.CRoleId.ToString());
+ response.RoleName = selectRole.CRoleName;
+ }
+
+ // Session.Add(response);
+
+ if (selectUser.CState == (byte)UserState.Active)
+ {
+ response.EC = ERROR_CODE.EC_OK;
+ }
+ else if (selectUser.CState == (byte)UserState.Inactive)
+ {
+ response.EC = ERROR_CODE.EC_USER_LOGIN_INAVTIVE;
+ }
+ else if (selectUser.CState == (byte)UserState.Block)
+ {
+ response.EC = ERROR_CODE.EC_USER_LOGIN_BLOCKED;
+ }
+ }
+ else
+ {
+ response.EC = ERROR_CODE.EC_USER_LOGIN_INVALID_PASSWORD;
+ }
+ }
+ else
+ {
+ response.EC = ERROR_CODE.EC_USER_LOGIN_NOT_EXIST;
+ Log4net.WriteLine($"{response.EC}", LogType.Error);
+ }
+ await context.CloseTransactionAsync(transaction);
+ }
+ }
+ catch (Exception e)
+ {
+ Log4net.WriteLine($"Select User Transaction Error", LogType.Exception);
+ Log4net.WriteLine(e);
+ }
+ }
+ }
+ }
+ }
+
+ return response;
+ }
+
+ public async Task UpdateLoginInfo(LoginModel loginModel, string? RefreshToken = "")
+ {
+ bool result = false;
+ bool transactionResult = true;
+
+ using (var scope = _scopeFactory.CreateScope())
+ {
+ var context = scope.ServiceProvider.GetRequiredService();
+ if (context is not null)
+ {
+ var selectUser = await context.TUsers.AsNoTracking().FirstOrDefaultAsync(x => x.CUserId.ToLower() == loginModel!.UserID!.ToLower());
+ if (selectUser is not null)
+ {
+ using (var transaction = await context.CreateTransactionAsync())
+ {
+ try
+ {
+ //user info
+ selectUser.CLastLoginDateTime = DateTime.Now;
+ context.Update(selectUser);
+
+ //refresh token
+ var findRefreshToken = await context.TRefreshTokens.AsNoTracking().FirstOrDefaultAsync(x => x.CAuid == selectUser.CAuid);
+ //null이면(없으면) add
+ if (findRefreshToken == null)
+ {
+ await context.AddAsync(new TRefreshToken
+ {
+ CAuid = selectUser.CAuid,
+ CRefreshToken = $"{RefreshToken}"
+ });
+ }
+ //있으면 update
+ else
+ {
+ findRefreshToken.CRefreshToken = $"{RefreshToken}";
+ context.Update(findRefreshToken);
+ }
+
+ //commit
+ Log4net.WriteLine(findRefreshToken?.ToJson(), LogType.Debug);
+
+ result = true;
+ }
+ catch (Exception ex)
+ {
+ Log4net.WriteLine(ex);
+ }
+
+ transactionResult = await context.CloseTransactionAsync(transaction);
+ }
+ }
+ else
+ {
+ Log4net.WriteLine($"Not Exist User {loginModel.UserID}", LogType.Error);
+ }
+
+ //db error
+ if (transactionResult == false)
+ {
+ Log4net.WriteLine($"Transaction Error", LogType.Error);
+ }
+ else
+ {
+ Log4net.WriteLine($"Transaction Success", LogType.DB);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public LogoutResponseModel LogoutUser(LogoutModel logoutModel)
+ {
+ LogoutResponseModel response = new LogoutResponseModel();
+ response.UserID = logoutModel.UserID;
+ response.EC = ERROR_CODE.EC_USER_LOGOUT_FAILED;
+
+ var session = Session.Find(x => x.UserID?.ToLower() == logoutModel?.UserID?.ToLower());
+ if (session != null)
+ {
+ Session.Remove(session);
+ response.EC = ERROR_CODE.EC_OK;
+ }
+
+ return response;
+ }
+ }
+}
diff --git a/Projects/WebApi/AuthApi/WeatherForecast.cs b/Projects/WebApi/AuthApi/WeatherForecast.cs
deleted file mode 100644
index 417734b..0000000
--- a/Projects/WebApi/AuthApi/WeatherForecast.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace AuthApi
-{
- public class WeatherForecast
- {
- public DateOnly Date { get; set; }
-
- public int TemperatureC { get; set; }
-
- public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-
- public string? Summary { get; set; }
- }
-}
diff --git a/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Context/AccountDbContext.cs b/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Context/AccountDbContext.cs
new file mode 100644
index 0000000..a053ff5
--- /dev/null
+++ b/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Context/AccountDbContext.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using WebApi.Library.DBContext.DB.DBContext.AccountDB.Tables;
+
+namespace WebApi.Library.DBContext.DB.DBContext.AccountDB.Context;
+
+public partial class AccountDbContext : DbContext
+{
+ public AccountDbContext()
+ {
+ }
+
+ public AccountDbContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ public virtual DbSet TRefreshTokens { get; set; }
+
+ public virtual DbSet TRoles { get; set; }
+
+ public virtual DbSet TUsers { get; set; }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
+ => optionsBuilder.UseSqlServer("server=127.0.0.1; user id=SystemX; password=X; database=AccountDB; TrustServerCertificate=true;");
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.CAuid).HasName("PK__tRefresh__FBF0855465EB95AB");
+
+ entity.ToTable("tRefreshToken");
+
+ entity.Property(e => e.CAuid)
+ .HasMaxLength(250)
+ .HasColumnName("cAuid");
+ entity.Property(e => e.CRefreshToken)
+ .HasMaxLength(1000)
+ .HasColumnName("cRefreshToken");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.CAuid).HasName("PK__tRole__FBF085540BB887D7");
+
+ entity.ToTable("tRole");
+
+ entity.Property(e => e.CAuid)
+ .HasMaxLength(250)
+ .HasColumnName("cAuid");
+ entity.Property(e => e.CRoleId).HasColumnName("cRoleID");
+ entity.Property(e => e.CRoleName)
+ .HasMaxLength(20)
+ .HasColumnName("cRoleName");
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.CUserId).HasName("PK__tUser__A75DC19A721265FF");
+
+ entity.ToTable("tUser");
+
+ entity.Property(e => e.CUserId)
+ .HasMaxLength(50)
+ .HasColumnName("cUserID");
+ entity.Property(e => e.CAuid)
+ .HasMaxLength(250)
+ .HasColumnName("cAuid");
+ entity.Property(e => e.CCreateDateTime).HasColumnName("cCreateDateTime");
+ entity.Property(e => e.CLastLoginDateTime).HasColumnName("cLastLoginDateTime");
+ entity.Property(e => e.CPasswordHashed)
+ .HasMaxLength(250)
+ .HasColumnName("cPasswordHashed");
+ entity.Property(e => e.CState).HasColumnName("cState");
+ });
+
+ OnModelCreatingPartial(modelBuilder);
+ }
+
+ partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
+}
diff --git a/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Tables/TRefreshToken.cs b/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Tables/TRefreshToken.cs
new file mode 100644
index 0000000..d807df1
--- /dev/null
+++ b/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Tables/TRefreshToken.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+
+namespace WebApi.Library.DBContext.DB.DBContext.AccountDB.Tables;
+
+public partial class TRefreshToken
+{
+ public string CAuid { get; set; } = null!;
+
+ public string CRefreshToken { get; set; } = null!;
+}
diff --git a/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Tables/TRole.cs b/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Tables/TRole.cs
new file mode 100644
index 0000000..bc6fa15
--- /dev/null
+++ b/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Tables/TRole.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+
+namespace WebApi.Library.DBContext.DB.DBContext.AccountDB.Tables;
+
+public partial class TRole
+{
+ public string CAuid { get; set; } = null!;
+
+ public byte CRoleId { get; set; }
+
+ public string CRoleName { get; set; } = null!;
+}
diff --git a/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Tables/TUser.cs b/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Tables/TUser.cs
new file mode 100644
index 0000000..4c4ec32
--- /dev/null
+++ b/Projects/WebApi/WebApi.Library.DBContext/DB/DBContext/AccountDB/Tables/TUser.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+
+namespace WebApi.Library.DBContext.DB.DBContext.AccountDB.Tables;
+
+public partial class TUser
+{
+ public string CUserId { get; set; } = null!;
+
+ public string CAuid { get; set; } = null!;
+
+ public string CPasswordHashed { get; set; } = null!;
+
+ public byte CState { get; set; }
+
+ public DateTime CCreateDateTime { get; set; }
+
+ public DateTime? CLastLoginDateTime { get; set; }
+}
diff --git a/Projects/WebApi/WebApi.Library.DBContext/WebApi.Library.DBContext.csproj b/Projects/WebApi/WebApi.Library.DBContext/WebApi.Library.DBContext.csproj
new file mode 100644
index 0000000..085266c
--- /dev/null
+++ b/Projects/WebApi/WebApi.Library.DBContext/WebApi.Library.DBContext.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/Projects/WebApi/WebApi.Library/Config/WebApiConfig.cs b/Projects/WebApi/WebApi.Library/Config/WebApiConfig.cs
new file mode 100644
index 0000000..51fb9fe
--- /dev/null
+++ b/Projects/WebApi/WebApi.Library/Config/WebApiConfig.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using SystemX.Core.Config;
+using SystemX.Core.Config.Model;
+
+namespace WebApi.Library.Config
+{
+ public class WebApiConfig : WebCommonConfig
+ {
+ [JsonPropertyName("Auth")]
+ public Auth? Auth { get; set; }
+
+ [JsonPropertyName("DataBase")]
+ public List? DataBase { get; set; }
+ }
+}
diff --git a/Projects/WebApi/WebApi.Library/WebApi.Library.csproj b/Projects/WebApi/WebApi.Library/WebApi.Library.csproj
new file mode 100644
index 0000000..8f25caa
--- /dev/null
+++ b/Projects/WebApi/WebApi.Library/WebApi.Library.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+ True
+
+
+
+ True
+
+
+
+
+ ..\..\DLL\SystemX.Core.dll
+
+
+
+
diff --git a/Projects/WebApi/WebApi.sln b/Projects/WebApi/WebApi.sln
index e587684..9b91b76 100644
--- a/Projects/WebApi/WebApi.sln
+++ b/Projects/WebApi/WebApi.sln
@@ -5,6 +5,15 @@ VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthApi", "AuthApi\AuthApi.csproj", "{321DD194-9455-48F7-A0BE-EF6E95881714}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Library", "WebApi.Library\WebApi.Library.csproj", "{1B109CFE-B860-4125-8F2B-06D95DE85E91}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Library.DBContext", "WebApi.Library.DBContext\WebApi.Library.DBContext.csproj", "{92599205-8D5B-4630-B669-AA390193BC9E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{C8D5274F-AC00-46C7-1F8D-E88E81087A52}"
+ ProjectSection(SolutionItems) = preProject
+ ..\Config\WebApi.AuthApi.Config.json = ..\Config\WebApi.AuthApi.Config.json
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +24,14 @@ Global
{321DD194-9455-48F7-A0BE-EF6E95881714}.Debug|Any CPU.Build.0 = Debug|Any CPU
{321DD194-9455-48F7-A0BE-EF6E95881714}.Release|Any CPU.ActiveCfg = Release|Any CPU
{321DD194-9455-48F7-A0BE-EF6E95881714}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B109CFE-B860-4125-8F2B-06D95DE85E91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B109CFE-B860-4125-8F2B-06D95DE85E91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B109CFE-B860-4125-8F2B-06D95DE85E91}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B109CFE-B860-4125-8F2B-06D95DE85E91}.Release|Any CPU.Build.0 = Release|Any CPU
+ {92599205-8D5B-4630-B669-AA390193BC9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {92599205-8D5B-4630-B669-AA390193BC9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {92599205-8D5B-4630-B669-AA390193BC9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {92599205-8D5B-4630-B669-AA390193BC9E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE