From f198f559356b6e8e1788f0022e3734ff80f1d40b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A8=81=E5=8B=9D=20=E5=BC=B5?= Date: Wed, 19 Feb 2025 20:10:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=B0=88=E6=A1=88=E6=AA=94?= =?UTF-8?q?=E6=A1=88=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 30 ++++ TCM_API.sln | 25 +++ .../Authorization/AllowAnonymousAttribute.cs | 5 + TCM_API/Authorization/AuthorizeAttribute.cs | 25 +++ TCM_API/Authorization/JwtMiddleware.cs | 26 ++++ TCM_API/Authorization/JwtUtils.cs | 111 +++++++++++++ TCM_API/Controllers/UsersController.cs | 93 +++++++++++ .../lamiter/Company_detail_tableController.cs | 118 ++++++++++++++ TCM_API/Dockerfile | 25 +++ TCM_API/Entities/User.cs | 20 +++ TCM_API/Helpers/AppSettings.cs | 6 + TCM_API/Models/AuthenticateRequest.cs | 12 ++ TCM_API/Models/AuthenticateResponse.cs | 26 ++++ TCM_API/Models/TEST_01.cs | 6 + .../Models/lamiter/Company_detail_table.cs | 14 ++ TCM_API/Program.cs | 146 ++++++++++++++++++ TCM_API/Properties/launchSettings.json | 52 +++++++ TCM_API/Services/SqlContext.cs | 41 +++++ TCM_API/Services/UserService.cs | 74 +++++++++ TCM_API/TCM_API.csproj | 30 ++++ TCM_API/TCM_API.http | 6 + TCM_API/ViewModels/Add_company.cs | 13 ++ TCM_API/appsettings.Development.json | 8 + TCM_API/appsettings.json | 12 ++ 24 files changed, 924 insertions(+) create mode 100644 .dockerignore create mode 100644 TCM_API.sln create mode 100644 TCM_API/Authorization/AllowAnonymousAttribute.cs create mode 100644 TCM_API/Authorization/AuthorizeAttribute.cs create mode 100644 TCM_API/Authorization/JwtMiddleware.cs create mode 100644 TCM_API/Authorization/JwtUtils.cs create mode 100644 TCM_API/Controllers/UsersController.cs create mode 100644 TCM_API/Controllers/lamiter/Company_detail_tableController.cs create mode 100644 TCM_API/Dockerfile create mode 100644 TCM_API/Entities/User.cs create mode 100644 TCM_API/Helpers/AppSettings.cs create mode 100644 TCM_API/Models/AuthenticateRequest.cs create mode 100644 TCM_API/Models/AuthenticateResponse.cs create mode 100644 TCM_API/Models/TEST_01.cs create mode 100644 TCM_API/Models/lamiter/Company_detail_table.cs create mode 100644 TCM_API/Program.cs create mode 100644 TCM_API/Properties/launchSettings.json create mode 100644 TCM_API/Services/SqlContext.cs create mode 100644 TCM_API/Services/UserService.cs create mode 100644 TCM_API/TCM_API.csproj create mode 100644 TCM_API/TCM_API.http create mode 100644 TCM_API/ViewModels/Add_company.cs create mode 100644 TCM_API/appsettings.Development.json create mode 100644 TCM_API/appsettings.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/TCM_API.sln b/TCM_API.sln new file mode 100644 index 0000000..6003c44 --- /dev/null +++ b/TCM_API.sln @@ -0,0 +1,25 @@ + +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}") = "TCM_API", "TCM_API\TCM_API.csproj", "{DB530787-F8A3-40D7-833F-0A7A9940E52C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DB530787-F8A3-40D7-833F-0A7A9940E52C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB530787-F8A3-40D7-833F-0A7A9940E52C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB530787-F8A3-40D7-833F-0A7A9940E52C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB530787-F8A3-40D7-833F-0A7A9940E52C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {76FA0356-8019-4761-B625-C7E084F29225} + EndGlobalSection +EndGlobal diff --git a/TCM_API/Authorization/AllowAnonymousAttribute.cs b/TCM_API/Authorization/AllowAnonymousAttribute.cs new file mode 100644 index 0000000..1312226 --- /dev/null +++ b/TCM_API/Authorization/AllowAnonymousAttribute.cs @@ -0,0 +1,5 @@ +namespace TCM_API.Authorization; + +[AttributeUsage(AttributeTargets.Method)] +public class AllowAnonymousAttribute : Attribute +{ } \ No newline at end of file diff --git a/TCM_API/Authorization/AuthorizeAttribute.cs b/TCM_API/Authorization/AuthorizeAttribute.cs new file mode 100644 index 0000000..e5407fc --- /dev/null +++ b/TCM_API/Authorization/AuthorizeAttribute.cs @@ -0,0 +1,25 @@ +namespace TCM_API.Authorization; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using TCM_API.Entities; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class AuthorizeAttribute : Attribute, IAuthorizationFilter +{ + public void OnAuthorization(AuthorizationFilterContext context) + { + // skip authorization if action is decorated with [AllowAnonymous] attribute + var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType().Any(); + if (allowAnonymous) + return; + + // authorization + var user = (User?)context.HttpContext.Items["User"]; + if (user == null) + { + // not logged in or role not authorized + context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized }; + } + } +} \ No newline at end of file diff --git a/TCM_API/Authorization/JwtMiddleware.cs b/TCM_API/Authorization/JwtMiddleware.cs new file mode 100644 index 0000000..a103996 --- /dev/null +++ b/TCM_API/Authorization/JwtMiddleware.cs @@ -0,0 +1,26 @@ +namespace TCM_API.Authorization; + +using TCM_API.Services; + +public class JwtMiddleware +{ + private readonly RequestDelegate _next; + + public JwtMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context, IUserService userService, IJwtUtils jwtUtils) + { + var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); + var userId = jwtUtils.ValidateJwtToken(token); + if (userId != null) + { + // attach user to context on successful jwt validation + context.Items["User"] = userService.GetById(userId.Value); + } + var stop = "1"; + await _next(context); + } +} \ No newline at end of file diff --git a/TCM_API/Authorization/JwtUtils.cs b/TCM_API/Authorization/JwtUtils.cs new file mode 100644 index 0000000..20f1b38 --- /dev/null +++ b/TCM_API/Authorization/JwtUtils.cs @@ -0,0 +1,111 @@ +namespace TCM_API.Authorization; + +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using TCM_API.Entities; +using TCM_API.Helpers; + +public interface IJwtUtils +{ + public string GenerateJwtToken(User user); + public int? ValidateJwtToken(string? token); +} + +public class JwtUtils : IJwtUtils +{ + private readonly AppSettings _appSettings; + + public JwtUtils(IOptions appSettings) + { + _appSettings = appSettings.Value; + + if (string.IsNullOrEmpty(_appSettings.Secret)) + throw new Exception("JWT secret not configured"); + } + + public string GenerateJwtToken(User user) + { + // generate token that is valid for 7 days + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_appSettings.Secret!); + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new[] + { + new Claim("id", user.id.ToString()), + new Claim("firstname", user.firstname ?? ""), // 加入 firstname + new Claim("lastname", user.lastname ?? ""), // 加入 lastname + new Claim("level", user.level ?? "") // 加入 lastname + }), + Expires = DateTime.UtcNow.AddDays(7), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + + public int? ValidateJwtToken(string? token) + { + if (token == null) + return null; + + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_appSettings.Secret!); + try + { + tokenHandler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false, + // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) + ClockSkew = TimeSpan.Zero + }, out SecurityToken validatedToken); + + var jwtToken = (JwtSecurityToken)validatedToken; + var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value); + + // return user id from JWT token if validation successful + return userId; + } + catch + { + // return null if validation fails + return null; + } + } + + + //0523 + public bool ValidateToken(string token) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var jwtSecret = "your_jwt_secret"; // JWT 密钥,应与生成令牌时使用的密钥相匹配 + + var validationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)), + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero // 设置为零以确保令牌过期时立即失效 + }; + + try + { + SecurityToken validatedToken; + tokenHandler.ValidateToken(token, validationParameters, out validatedToken); + return true; + } + catch + { + return false; + } + } + +} \ No newline at end of file diff --git a/TCM_API/Controllers/UsersController.cs b/TCM_API/Controllers/UsersController.cs new file mode 100644 index 0000000..6854df2 --- /dev/null +++ b/TCM_API/Controllers/UsersController.cs @@ -0,0 +1,93 @@ +namespace WebApi.Controllers; + +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; +using NuGet.Common; +using System.IdentityModel.Tokens.Jwt; +using TCM_API.Authorization; +using TCM_API.Models; +using TCM_API.Services; + +[ApiController] +[Authorize] +[Route("[controller]")] +public class UsersController : ControllerBase +{ + private IUserService _userService; + + public UsersController(IUserService userService) + { + _userService = userService; + } + + [AllowAnonymous] + [HttpPost("authenticate")] + public IActionResult Authenticate(AuthenticateRequest model) + { + var response = _userService.Authenticate(model); + + if (response == null) + return BadRequest(new { message = "Username or password is incorrect" }); + + // 将令牌添加到响应头中 + Response.Headers.Add("Authorization", "Bearer " + response.Token); + + // 将令牌保存在Cookie或其他适当的位置 + Response.Cookies.Append("token", response.Token); + return Ok(response); + // 重定向到另一个页面 + //return RedirectToAction("/Park_spaces/Parking_spaces_total_table"); + //return RedirectToAction("Parking_spaces_total_table", "Park_spaces"); + } + [HttpGet("token_check")] + public IActionResult Token() + { + return Ok(); + } + + + + [HttpGet("token_check_user")] + public IActionResult GetUserData() + { + var tokenStr = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); + + if (string.IsNullOrEmpty(tokenStr)) + { + return Unauthorized("Token is missing or invalid."); + } + + var tokenHandler = new JwtSecurityTokenHandler(); + try + { + var token = tokenHandler.ReadJwtToken(tokenStr); + + // 轉換 payload 為字典 + var payloadData = token.Payload + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToString()); + + // 回傳 payload 作為 JSON + return Ok(payloadData); + } + catch (Exception ex) + { + return BadRequest($"Error parsing token: {ex.Message}"); + } + } + + + + [HttpGet] + public IActionResult GetAll() + { + var users = _userService.GetAll(); + return Ok(users); + } + + //[HttpPost("create_manage")] + + + + + +} diff --git a/TCM_API/Controllers/lamiter/Company_detail_tableController.cs b/TCM_API/Controllers/lamiter/Company_detail_tableController.cs new file mode 100644 index 0000000..475dea8 --- /dev/null +++ b/TCM_API/Controllers/lamiter/Company_detail_tableController.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using TCM_API.Models; +using TCM_API.Entities; +using TCM_API.Services; +using TCM_API.Authorization; +using TCM_API.ViewModels; + +namespace TCM_API.Controllers.lamiter +{ + [Route("api/[controller]")] + [ApiController] + //[Authorize] + public class Company_detail_tableController : ControllerBase + { + private readonly SqlContext _context; + + public Company_detail_tableController(SqlContext context) + { + _context = context; + } + + #region 獲取所有企業 + /// + /// 獲取所有企業 + /// + [HttpGet("get_all_campany")] + public async Task> GetCompany_all_deatil() + { + + var data = await(from c in _context.company_detail_table + select new + { + campant_name = c.campant_name, + guid = c.guid + + }).ToListAsync(); + return Ok( data); + } + #endregion + + #region 獲取單一企業 + [HttpGet("get_campany-{guid}")] + public async Task> GetCompany_deatil(string guid) + { + var data = await _context.company_detail_table.FindAsync(guid); + if (data == null) + { + return NotFound(); + } + return data; + } + #endregion + + + #region 新增企業 + /// + /// 新增企業 + /// + [HttpPost("Add_campany")] + public async Task> PostCompany_detail_table_1(Add_company add_company) + { + // 創建Guid + Guid new_guid = Guid.NewGuid(); + + + //創建 公司詳細 + Company_detail_table company_detail_table = new Company_detail_table(); + company_detail_table.guid = new_guid.ToString(); + company_detail_table.data_create_time = DateTime.Now; + company_detail_table.campant_name = add_company.lastname; + + //創建 公司帳號 + User user = new User(); + user.firstname = "company"; + user.lastname = add_company.lastname; + user.email = add_company.email; + user.password = add_company.password; + user.username = add_company.username; + user.level = "8"; + user.guid = new_guid.ToString(); + + _context.company_detail_table.Add(company_detail_table); + _context.user_table.Add(user); + try + { + await _context.SaveChangesAsync(); + } + catch (DbUpdateException) + { + if (Company_detail_tableExists(company_detail_table.guid)) + { + return Conflict(); + } + else + { + throw; + } + } + + return Ok(); + } + #endregion + + + + + private bool Company_detail_tableExists(string id) + { + return _context.company_detail_table.Any(e => e.guid == id); + } + } +} diff --git a/TCM_API/Dockerfile b/TCM_API/Dockerfile new file mode 100644 index 0000000..e4d04ab --- /dev/null +++ b/TCM_API/Dockerfile @@ -0,0 +1,25 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["TCM_API/TCM_API.csproj", "TCM_API/"] +RUN dotnet restore "./TCM_API/TCM_API.csproj" +COPY . . +WORKDIR "/src/TCM_API" +RUN dotnet build "./TCM_API.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./TCM_API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "TCM_API.dll"] \ No newline at end of file diff --git a/TCM_API/Entities/User.cs b/TCM_API/Entities/User.cs new file mode 100644 index 0000000..547f94b --- /dev/null +++ b/TCM_API/Entities/User.cs @@ -0,0 +1,20 @@ +namespace TCM_API.Entities; + +using MessagePack; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +public class User +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int id { get; set; } + public string? firstname { get; set; } + public string? lastname { get; set; } + public string? email { get; set; } + public string? username { get; set; } + public string? level { get; set; } + public string? guid { get; set; } + + [JsonIgnore] + public string? password { get; set; } +} \ No newline at end of file diff --git a/TCM_API/Helpers/AppSettings.cs b/TCM_API/Helpers/AppSettings.cs new file mode 100644 index 0000000..299f362 --- /dev/null +++ b/TCM_API/Helpers/AppSettings.cs @@ -0,0 +1,6 @@ +namespace TCM_API.Helpers; + +public class AppSettings +{ + public string? Secret { get; set; } +} \ No newline at end of file diff --git a/TCM_API/Models/AuthenticateRequest.cs b/TCM_API/Models/AuthenticateRequest.cs new file mode 100644 index 0000000..0222b67 --- /dev/null +++ b/TCM_API/Models/AuthenticateRequest.cs @@ -0,0 +1,12 @@ +namespace TCM_API.Models; + +using System.ComponentModel.DataAnnotations; + +public class AuthenticateRequest +{ + [Required] + public string? Username { get; set; } + + [Required] + public string? Password { get; set; } +} \ No newline at end of file diff --git a/TCM_API/Models/AuthenticateResponse.cs b/TCM_API/Models/AuthenticateResponse.cs new file mode 100644 index 0000000..83eff3e --- /dev/null +++ b/TCM_API/Models/AuthenticateResponse.cs @@ -0,0 +1,26 @@ +namespace TCM_API.Models; + +using TCM_API.Entities; + +public class AuthenticateResponse +{ + public int id { get; set; } + public string? firstname { get; set; } + public string? lastname { get; set; } + public string? username { get; set; } + public string? email { get; set; } + public string? level { get; set; } + public string Token { get; set; } + + + public AuthenticateResponse(User user, string token) + { + id = user.id; + firstname = user.firstname; + lastname = user.lastname; + username = user.username; + level = user.level; + email = user.email; + Token = token; + } +} \ No newline at end of file diff --git a/TCM_API/Models/TEST_01.cs b/TCM_API/Models/TEST_01.cs new file mode 100644 index 0000000..c08ba45 --- /dev/null +++ b/TCM_API/Models/TEST_01.cs @@ -0,0 +1,6 @@ +namespace TCM_API.Models +{ + public class TEST_01 + { + } +} diff --git a/TCM_API/Models/lamiter/Company_detail_table.cs b/TCM_API/Models/lamiter/Company_detail_table.cs new file mode 100644 index 0000000..3af3b3a --- /dev/null +++ b/TCM_API/Models/lamiter/Company_detail_table.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace TCM_API.Models +{ + public class Company_detail_table + { + public string? campant_name { get; set; } + public string? guid { get; set; } + public DateTime? data_create_time { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int id { get; set; } + } +} diff --git a/TCM_API/Program.cs b/TCM_API/Program.cs new file mode 100644 index 0000000..bca39d3 --- /dev/null +++ b/TCM_API/Program.cs @@ -0,0 +1,146 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using TCM_API.Authorization; +using TCM_API.Helpers; +using System.Configuration; +using TCM_API.Services; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using Microsoft.OpenApi.Models; + + + +var builder = WebApplication.CreateBuilder(args); +//在 ASP.NET Core 中啟用 CORS (跨原始來源要求) +builder.Services.AddCors(); +// Add services to the container. +builder.Services.AddControllers(); + +// 連線PostgreSQL資料庫 +var connectionString = "Server=leovip125.ddns.net;UserID=postgres;Password=vip125;Database=TCM;port=5432;Search Path=public;CommandTimeout=1800"; +builder.Services.AddDbContext(opt => opt.UseNpgsql(connectionString)); + + + +//身分驗證 +//add services to DI container +{ + var services = builder.Services; + services.AddCors(); + services.AddControllers(); + + // configure strongly typed settings object + services.Configure(builder.Configuration.GetSection("AppSettings")); + + // 配置JWT身份验证 + var jwtSettings = builder.Configuration.GetSection("AppSettings").Get(); + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = false, + ValidateAudience = false, + ValidateIssuerSigningKey = true, + //ValidIssuer = "your_issuer", + // ValidAudience = "your_audience", + ClockSkew = TimeSpan.Zero, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Secret)) + }; + }); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "TCM_API", Version = "v1" }); + + // Configure Swagger to use JWT authentication + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer" + }); + + // 将JWT令牌作为所有端点的要求添加到Swagger文档 + //ˇc.OperationFilter(); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] { } + } + }); + }); + // configure DI for application services + services.AddScoped(); + services.AddScoped(); + + // 注册 HttpClient 服务 + services.AddHttpClient(); + +} + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + + +//身分驗證 +// configure HTTP request pipeline +{ + // global cors policy + app.UseCors(x => x + .AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + + // custom jwt auth middleware + app.UseMiddleware(); + + app.MapControllers(); +} + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + //app.UseSwaggerUI(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "TCM_API"); + }); +} +//在 ASP.NET Core 中啟用 CORS (跨原始來源要求) +// Shows UseCors with CorsPolicyBuilder. +app.UseCors(builder => +{ + builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); +}); + +app.UseHttpsRedirection(); + +app.UseAuthentication(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); + diff --git a/TCM_API/Properties/launchSettings.json b/TCM_API/Properties/launchSettings.json new file mode 100644 index 0000000..f40ede0 --- /dev/null +++ b/TCM_API/Properties/launchSettings.json @@ -0,0 +1,52 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5291" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7226;http://localhost:5291" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:9106", + "sslPort": 44325 + } + } +} \ No newline at end of file diff --git a/TCM_API/Services/SqlContext.cs b/TCM_API/Services/SqlContext.cs new file mode 100644 index 0000000..b5d9270 --- /dev/null +++ b/TCM_API/Services/SqlContext.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration.Json; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using TCM_API.Models; +using TCM_API.Entities; + + +namespace TCM_API.Services +{ + public class SqlContext : DbContext + { + + public SqlContext(DbContextOptions options) : base(options) + { + //連接PostgreSQL + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true); + } + + + //public DbSet e_table_v { get; set; } = null!; + //public DbSet test_0525 { get; set; } = null!; // Model名稱 test_0330 SQL名稱 + public DbSet user_table { get; set; } = null!; + public DbSet company_detail_table { get; set; } = null!; + protected override void OnModelCreating(ModelBuilder builder) + { + + base.OnModelCreating(builder); + //builder.Entity().HasKey(o => new { o.user_id }); //Primary Key + builder.Entity().HasKey(o => new { o.id }); //Primary Key + builder.Entity().HasKey(o => new { o.guid }); + + } + + + } + +} \ No newline at end of file diff --git a/TCM_API/Services/UserService.cs b/TCM_API/Services/UserService.cs new file mode 100644 index 0000000..af9994e --- /dev/null +++ b/TCM_API/Services/UserService.cs @@ -0,0 +1,74 @@ +namespace TCM_API.Services; +using Microsoft.EntityFrameworkCore; +using TCM_API.Authorization; +using TCM_API.Entities; +using TCM_API.Models; + +public interface IUserService +{ + AuthenticateResponse? Authenticate(AuthenticateRequest model); + IEnumerable GetAll(); + User? GetById(int id); +} + +public class UserService : IUserService +{ + /* + // users hardcoded for simplicity, store in a db with hashed passwords in production applications + private List user_test = new List + { + new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" }, + new User { Id = 2, FirstName = "Test", LastName = "User", Username = "admin", Password = "admin" } + }; + + public DbSet user_test { get; set; } = null!; + + + public List GetUsers () + { + return _dbContext.user_test.ToList(); + } + + */ + + private readonly IJwtUtils _jwtUtils; + + public UserService(IJwtUtils jwtUtils, SqlContext dbContext) + { + _jwtUtils = jwtUtils; + _dbContext = dbContext; + } + + + private readonly SqlContext _dbContext; + + + public AuthenticateResponse? Authenticate(AuthenticateRequest model) + { + var user = _dbContext.user_table.SingleOrDefault( + x => (x.username == model.Username || x.email == model.Username) + && x.password == model.Password); + + // return null if user not found + if (user == null) return null; + + // authentication successful so generate jwt token + var token = _jwtUtils.GenerateJwtToken(user); + + return new AuthenticateResponse(user, token); + } + + + public IEnumerable GetAll() + { + return _dbContext.user_table; + } + + public User? GetById(int id) + { + return _dbContext.user_table.FirstOrDefault(x => x.id == id); + } + + + +} \ No newline at end of file diff --git a/TCM_API/TCM_API.csproj b/TCM_API/TCM_API.csproj new file mode 100644 index 0000000..afb7f68 --- /dev/null +++ b/TCM_API/TCM_API.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + 6b0f798d-70cd-4d60-b552-6e6488f58399 + Linux + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/TCM_API/TCM_API.http b/TCM_API/TCM_API.http new file mode 100644 index 0000000..37ee123 --- /dev/null +++ b/TCM_API/TCM_API.http @@ -0,0 +1,6 @@ +@TCM_API_HostAddress = http://localhost:5291 + +GET {{TCM_API_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/TCM_API/ViewModels/Add_company.cs b/TCM_API/ViewModels/Add_company.cs new file mode 100644 index 0000000..1ba1de8 --- /dev/null +++ b/TCM_API/ViewModels/Add_company.cs @@ -0,0 +1,13 @@ +namespace TCM_API.ViewModels +{ + public class Add_company + { + public string? guid { get; set; } + public string? firstname { get; set; } + public string? lastname { get; set; } + public string? email { get; set; } + public string? username { get; set; } + public string? password { get; set; } + public string? level { get; set; } + } +} diff --git a/TCM_API/appsettings.Development.json b/TCM_API/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/TCM_API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/TCM_API/appsettings.json b/TCM_API/appsettings.json new file mode 100644 index 0000000..467ce11 --- /dev/null +++ b/TCM_API/appsettings.json @@ -0,0 +1,12 @@ +{ + "AppSettings": { + "Secret": "TCM token test jwt lamiter local 1234567" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}