diff options
Diffstat (limited to 'src/FundLab.Api')
| -rw-r--r-- | src/FundLab.Api/App.fs | 53 | ||||
| -rw-r--r-- | src/FundLab.Api/Authentication.fs | 25 | ||||
| -rw-r--r-- | src/FundLab.Api/FundLab.Api.fsproj | 20 | ||||
| -rw-r--r-- | src/FundLab.Api/Health.fs | 16 | ||||
| -rw-r--r-- | src/FundLab.Api/Program.fs | 15 |
5 files changed, 129 insertions, 0 deletions
diff --git a/src/FundLab.Api/App.fs b/src/FundLab.Api/App.fs new file mode 100644 index 0000000..44628ab --- /dev/null +++ b/src/FundLab.Api/App.fs @@ -0,0 +1,53 @@ +namespace FundLab.Api + +open System +open Giraffe +open Microsoft.AspNetCore.Http + +type EmptyPortfolioResponse = + { + status: string + message: string + } + +module App = + let private unauthorized : HttpHandler = + setStatusCode 401 + >=> setHttpHeader "WWW-Authenticate" "Bearer" + >=> text "Unauthorized" + + let private requireBearer (next: HttpFunc) (ctx: HttpContext) = + let expectedToken = + Environment.GetEnvironmentVariable("FUND_LAB_AUTH_TOKEN") + |> Option.ofObj + |> Option.defaultValue "" + + let authorizationHeader = ctx.Request.Headers.Authorization.ToString() + + match Authentication.authorize expectedToken authorizationHeader with + | AuthDecision.Authorized -> next ctx + | AuthDecision.Unauthorized -> unauthorized next ctx + + let private health : HttpHandler = + json ({ + service = "fund-lab-api" + status = "ok" + } : HealthResponse) + + let private emptyPortfolio : HttpHandler = + json { + status = "empty" + message = "尚未创建基金/尚未选择投资" + } + + let createApplication () : HttpHandler = + choose [ + GET >=> route "/health" >=> health + subRoute "/api" ( + requireBearer + >=> choose [ + GET >=> route "/portfolio/summary" >=> emptyPortfolio + ] + ) + setStatusCode 404 >=> text "Not Found" + ] diff --git a/src/FundLab.Api/Authentication.fs b/src/FundLab.Api/Authentication.fs new file mode 100644 index 0000000..e98d71e --- /dev/null +++ b/src/FundLab.Api/Authentication.fs @@ -0,0 +1,25 @@ +namespace FundLab.Api + +open System +open System.Security.Cryptography +open System.Text + +type AuthDecision = + | Authorized + | Unauthorized + +module Authentication = + let authorize (expectedToken: string) (authorizationHeader: string) : AuthDecision = + if String.IsNullOrWhiteSpace expectedToken + || String.IsNullOrWhiteSpace authorizationHeader + || not (authorizationHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) then + AuthDecision.Unauthorized + else + let presentedToken = authorizationHeader.Substring("Bearer ".Length) + let expectedBytes = Encoding.UTF8.GetBytes expectedToken + let presentedBytes = Encoding.UTF8.GetBytes presentedToken + + if CryptographicOperations.FixedTimeEquals(expectedBytes, presentedBytes) then + AuthDecision.Authorized + else + AuthDecision.Unauthorized diff --git a/src/FundLab.Api/FundLab.Api.fsproj b/src/FundLab.Api/FundLab.Api.fsproj new file mode 100644 index 0000000..81952df --- /dev/null +++ b/src/FundLab.Api/FundLab.Api.fsproj @@ -0,0 +1,20 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + <PropertyGroup> + <TargetFramework>net8.0</TargetFramework> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Giraffe" Version="6.0.0" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\FundLab.Domain\FundLab.Domain.fsproj" /> + </ItemGroup> + <ItemGroup> + <Compile Include="Authentication.fs" /> + <Compile Include="Health.fs" /> + <Compile Include="App.fs" /> + <Compile Include="Program.fs" /> + </ItemGroup> +</Project> diff --git a/src/FundLab.Api/Health.fs b/src/FundLab.Api/Health.fs new file mode 100644 index 0000000..719446f --- /dev/null +++ b/src/FundLab.Api/Health.fs @@ -0,0 +1,16 @@ +namespace FundLab.Api + +open System.Text.Json + +type HealthResponse = + { + service: string + status: string + } + +module Health = + let payload (serviceName: string) : string = + JsonSerializer.Serialize<HealthResponse> { + service = serviceName + status = "ok" + } diff --git a/src/FundLab.Api/Program.fs b/src/FundLab.Api/Program.fs new file mode 100644 index 0000000..d5cc0b2 --- /dev/null +++ b/src/FundLab.Api/Program.fs @@ -0,0 +1,15 @@ +module FundLab.Api.Program + +open Giraffe +open Microsoft.AspNetCore.Builder +open Microsoft.Extensions.DependencyInjection + +[<EntryPoint>] +let main argv = + let builder = WebApplication.CreateBuilder(argv) + builder.Services.AddGiraffe() |> ignore + + let app = builder.Build() + app.UseGiraffe(App.createApplication()) + app.Run() + 0 |
