Implement Phases 2-5: Frontend, AI/RAG, PDF Generation (#22)
* Fix NuGet versions and add InitialCreate EF Core migration
- Update AWSSDK.SQS and AWSSDK.SecretsManager to 3.7.500.0 (actual available versions)
- Update AWSSDK.Extensions.NETCore.Setup to 3.7.400
- Generate InitialCreate migration for PostgreSQL (all 8 entities)
- Build verified: 0 errors, 0 warnings
* Implement Dispatcher Frontend (Phase 2)
React 19 + MUI v7 + TypeScript + Vite SPA matching SHOC patterns:
Redux Toolkit (auth/ui slices), TanStack React Query, axios interceptors,
react-toastify, Cognito OAuth PKCE login flow, paginated proposal list,
new proposal form with customer autocomplete and vendor PDF upload,
read-only proposal detail with status stepper timeline.
* Add AuthController for Cognito code exchange and .env.example
Backend endpoint POST /api/auth/callback exchanges the OAuth
authorization code with Cognito's token endpoint, auto-provisions
the user in the DB, and returns the access token to the frontend.
* Implement Admin Frontend Experience (Phase 3)
Three-panel admin workspace: left reference panel (submission details,
vendor data), center editor (refined scope, inline line item table with
reorder/add/remove/pricing), right similar proposals panel (KB results
with pull-to-editor). Admin dashboard with stats cards and proposal
queue table. Approval flow with confirmation dialog, mark-as-sent,
and create-revision actions. Role-based sidebar navigation.
* Implement backend dev mode, internal API auth, and service layer enhancements
- Add dev-login endpoint with local JWT signing for local development
- Add InternalApiKeyMiddleware with timing-safe comparison for Lambda-to-API auth
- Add DevS3Service and NoOpJobPublisher for running without AWS services
- Implement CurrentUserService cascading user resolution (ID → sub → email → create)
- Add async ResolveAsync() to avoid synchronous DB calls in request pipeline
- Add /proposals/stats endpoint for efficient server-side status counts
- Guard status transitions: only allow Draft → InReview via update endpoint
- Add vendor proposals, generated PDFs, and similar proposals controllers
- Add ISimilarProposalService and SimilarProposalService
- Add [Authorize] to AddSimilarReference endpoint
* Implement Lambda functions for PDF processing, suggestions, and library ingest
- pdf-extract: Parse vendor PDFs with pdfplumber, fallback to Claude multimodal
- pdf-generate: Generate branded proposal PDFs with reportlab Platypus
- library-ingest: Format approved proposals as markdown and sync to Bedrock KB
- suggestions: Query KB for similar proposals, generate line items via Claude
- All Lambdas use internal API key auth and cold-start secret caching
- Fix pdf_path unbound variable in pdf-extract error handling
* Add Bedrock Knowledge Base, OpenSearch Serverless, and SQS message filtering
- Provision OpenSearch Serverless collection for vector search
- Create Bedrock Knowledge Base with Titan embedding model
- Configure S3 data source with fixed-size chunking (512 tokens, 20% overlap)
- Add suggestions Lambda with SQS event source filtering
- Scope bedrock:InvokeModel IAM to specific model ARN patterns
- Add internal API key secret in Secrets Manager
- Add log retention (2 months) to all Lambda functions
- Add docker-compose.yml for local PostgreSQL
* Apply SHOC design system styling across frontend
- Rewrite theme with SHOC palette (#0c4f6f primary, Nunito font, 4px radius)
- Add global CSS with Google Fonts import for Nunito
- Redesign Topbar with avatar initials, role subtitle, gradient header
- Redesign Sidebar with 220px width, section headers, active state border
- Restyle LoginPage with SHOC branded card and dev-mode role selector
- Update AdminDashboard KPI cards to centered SHOC style
- Add devLogin API method for local development auth flow
* Fix frontend navigation bugs, differentiate Dashboard from Proposals list
- Fix double nav selection by adding isNavActive() with ALL_NAV_PATHS set
- Fix /admin/users routing to placeholder instead of redirect to /
- Fix ProposalDetailPage Back button navigating to / instead of /proposals
- Differentiate Dashboard (KPI cards + recent 5) from ProposalListPage (full paginated table)
- Dashboard now uses dedicated /proposals/stats endpoint for accurate counts
- Fix adminApi.getPdf dead code (axios rejects before status check)
- Wire up PDF generation button in AdminWorkspace
- Adjust layout: 220px drawer, 10px content padding, 64px toolbar height
* Add appsettings.Development.json to gitignore
Prevent dev-only signing keys and connection strings from being committed.
* Fix CI failures: unused Python imports and CDK synth asset path
CDK synth job needs the .NET API published first so the Lambda asset
path exists. Python lint had 3 unused imports in pdf-generate.
* Apply ruff formatting to all Lambda Python files
2026-05-17 13:06:23 -04:00
|
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
|
|
|
using System.Net.Http.Headers;
|
|
|
|
|
using System.Security.Claims;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
|
using ProposalSystem.Domain.Entities;
|
|
|
|
|
using ProposalSystem.Infrastructure.Data;
|
|
|
|
|
|
|
|
|
|
namespace ProposalSystem.Api.Controllers;
|
|
|
|
|
|
|
|
|
|
[ApiController]
|
|
|
|
|
[Route("api/[controller]")]
|
|
|
|
|
public class AuthController : ControllerBase
|
|
|
|
|
{
|
|
|
|
|
private readonly ProposalDbContext _db;
|
|
|
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
|
|
|
|
private readonly IConfiguration _config;
|
|
|
|
|
|
|
|
|
|
public AuthController(ProposalDbContext db, IHttpClientFactory httpClientFactory, IConfiguration config)
|
|
|
|
|
{
|
|
|
|
|
_db = db;
|
|
|
|
|
_httpClientFactory = httpClientFactory;
|
|
|
|
|
_config = config;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPost("callback")]
|
|
|
|
|
public async Task<ActionResult<AuthResponse>> Callback([FromBody] AuthCallbackRequest request, CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
var domain = _config["Auth:CognitoDomain"];
|
|
|
|
|
var clientId = _config["Auth:ClientId"];
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(domain) || string.IsNullOrEmpty(clientId))
|
|
|
|
|
return StatusCode(500, new { message = "Auth not configured" });
|
|
|
|
|
|
|
|
|
|
var tokenResponse = await ExchangeCodeAsync(domain, clientId, request.Code, request.RedirectUri, ct);
|
|
|
|
|
if (tokenResponse == null)
|
|
|
|
|
return BadRequest(new { message = "Failed to exchange authorization code" });
|
|
|
|
|
|
|
|
|
|
var handler = new JwtSecurityTokenHandler();
|
|
|
|
|
var idToken = handler.ReadJwtToken(tokenResponse.IdToken);
|
|
|
|
|
|
|
|
|
|
var sub = idToken.Claims.FirstOrDefault(c => c.Type == "sub")?.Value
|
|
|
|
|
?? throw new InvalidOperationException("No sub claim in ID token");
|
|
|
|
|
var email = idToken.Claims.FirstOrDefault(c => c.Type == "email")?.Value ?? "";
|
|
|
|
|
var name = idToken.Claims.FirstOrDefault(c => c.Type == "name")?.Value
|
|
|
|
|
?? idToken.Claims.FirstOrDefault(c => c.Type == "cognito:username")?.Value
|
|
|
|
|
?? email.Split('@')[0];
|
|
|
|
|
var groups = idToken.Claims.Where(c => c.Type == "cognito:groups").Select(c => c.Value).ToList();
|
|
|
|
|
|
|
|
|
|
var role = groups.Contains("sysadmins") ? UserRole.SysAdmin
|
|
|
|
|
: groups.Contains("admins") ? UserRole.Admin
|
|
|
|
|
: UserRole.Dispatcher;
|
|
|
|
|
|
|
|
|
|
var user = await _db.Users.FirstOrDefaultAsync(u => u.CognitoSub == sub, ct);
|
|
|
|
|
if (user == null)
|
|
|
|
|
{
|
|
|
|
|
user = new User
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
CognitoSub = sub,
|
|
|
|
|
Email = email,
|
|
|
|
|
DisplayName = name,
|
|
|
|
|
Role = role,
|
|
|
|
|
IsActive = true,
|
|
|
|
|
CreatedAt = DateTime.UtcNow,
|
|
|
|
|
UpdatedAt = DateTime.UtcNow,
|
|
|
|
|
};
|
|
|
|
|
_db.Users.Add(user);
|
|
|
|
|
await _db.SaveChangesAsync(ct);
|
|
|
|
|
}
|
|
|
|
|
else if (user.Email != email || user.DisplayName != name)
|
|
|
|
|
{
|
|
|
|
|
user.Email = email;
|
|
|
|
|
user.DisplayName = name;
|
|
|
|
|
user.UpdatedAt = DateTime.UtcNow;
|
|
|
|
|
await _db.SaveChangesAsync(ct);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(new AuthResponse(
|
|
|
|
|
user.Id.ToString(),
|
|
|
|
|
user.Email,
|
|
|
|
|
user.DisplayName,
|
|
|
|
|
user.Role.ToString(),
|
|
|
|
|
tokenResponse.AccessToken
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPost("dev-login")]
|
|
|
|
|
public async Task<ActionResult<AuthResponse>> DevLogin([FromBody] DevLoginRequest request, CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
var devMode = _config.GetValue<bool>("Auth:DevMode");
|
|
|
|
|
if (!devMode)
|
|
|
|
|
return NotFound();
|
|
|
|
|
|
|
|
|
|
var signingKey = _config["Auth:DevSigningKey"];
|
|
|
|
|
if (string.IsNullOrEmpty(signingKey))
|
|
|
|
|
return StatusCode(500, new { message = "Dev signing key not configured" });
|
|
|
|
|
|
|
|
|
|
var role = Enum.TryParse<UserRole>(request.Role, true, out var parsed) ? parsed : UserRole.Admin;
|
|
|
|
|
|
|
|
|
|
var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == request.Email, ct);
|
|
|
|
|
if (user == null)
|
|
|
|
|
{
|
|
|
|
|
user = new User
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.NewGuid(),
|
2026-05-20 17:24:01 -04:00
|
|
|
CognitoSub = $"dev-{request.Email}",
|
Implement Phases 2-5: Frontend, AI/RAG, PDF Generation (#22)
* Fix NuGet versions and add InitialCreate EF Core migration
- Update AWSSDK.SQS and AWSSDK.SecretsManager to 3.7.500.0 (actual available versions)
- Update AWSSDK.Extensions.NETCore.Setup to 3.7.400
- Generate InitialCreate migration for PostgreSQL (all 8 entities)
- Build verified: 0 errors, 0 warnings
* Implement Dispatcher Frontend (Phase 2)
React 19 + MUI v7 + TypeScript + Vite SPA matching SHOC patterns:
Redux Toolkit (auth/ui slices), TanStack React Query, axios interceptors,
react-toastify, Cognito OAuth PKCE login flow, paginated proposal list,
new proposal form with customer autocomplete and vendor PDF upload,
read-only proposal detail with status stepper timeline.
* Add AuthController for Cognito code exchange and .env.example
Backend endpoint POST /api/auth/callback exchanges the OAuth
authorization code with Cognito's token endpoint, auto-provisions
the user in the DB, and returns the access token to the frontend.
* Implement Admin Frontend Experience (Phase 3)
Three-panel admin workspace: left reference panel (submission details,
vendor data), center editor (refined scope, inline line item table with
reorder/add/remove/pricing), right similar proposals panel (KB results
with pull-to-editor). Admin dashboard with stats cards and proposal
queue table. Approval flow with confirmation dialog, mark-as-sent,
and create-revision actions. Role-based sidebar navigation.
* Implement backend dev mode, internal API auth, and service layer enhancements
- Add dev-login endpoint with local JWT signing for local development
- Add InternalApiKeyMiddleware with timing-safe comparison for Lambda-to-API auth
- Add DevS3Service and NoOpJobPublisher for running without AWS services
- Implement CurrentUserService cascading user resolution (ID → sub → email → create)
- Add async ResolveAsync() to avoid synchronous DB calls in request pipeline
- Add /proposals/stats endpoint for efficient server-side status counts
- Guard status transitions: only allow Draft → InReview via update endpoint
- Add vendor proposals, generated PDFs, and similar proposals controllers
- Add ISimilarProposalService and SimilarProposalService
- Add [Authorize] to AddSimilarReference endpoint
* Implement Lambda functions for PDF processing, suggestions, and library ingest
- pdf-extract: Parse vendor PDFs with pdfplumber, fallback to Claude multimodal
- pdf-generate: Generate branded proposal PDFs with reportlab Platypus
- library-ingest: Format approved proposals as markdown and sync to Bedrock KB
- suggestions: Query KB for similar proposals, generate line items via Claude
- All Lambdas use internal API key auth and cold-start secret caching
- Fix pdf_path unbound variable in pdf-extract error handling
* Add Bedrock Knowledge Base, OpenSearch Serverless, and SQS message filtering
- Provision OpenSearch Serverless collection for vector search
- Create Bedrock Knowledge Base with Titan embedding model
- Configure S3 data source with fixed-size chunking (512 tokens, 20% overlap)
- Add suggestions Lambda with SQS event source filtering
- Scope bedrock:InvokeModel IAM to specific model ARN patterns
- Add internal API key secret in Secrets Manager
- Add log retention (2 months) to all Lambda functions
- Add docker-compose.yml for local PostgreSQL
* Apply SHOC design system styling across frontend
- Rewrite theme with SHOC palette (#0c4f6f primary, Nunito font, 4px radius)
- Add global CSS with Google Fonts import for Nunito
- Redesign Topbar with avatar initials, role subtitle, gradient header
- Redesign Sidebar with 220px width, section headers, active state border
- Restyle LoginPage with SHOC branded card and dev-mode role selector
- Update AdminDashboard KPI cards to centered SHOC style
- Add devLogin API method for local development auth flow
* Fix frontend navigation bugs, differentiate Dashboard from Proposals list
- Fix double nav selection by adding isNavActive() with ALL_NAV_PATHS set
- Fix /admin/users routing to placeholder instead of redirect to /
- Fix ProposalDetailPage Back button navigating to / instead of /proposals
- Differentiate Dashboard (KPI cards + recent 5) from ProposalListPage (full paginated table)
- Dashboard now uses dedicated /proposals/stats endpoint for accurate counts
- Fix adminApi.getPdf dead code (axios rejects before status check)
- Wire up PDF generation button in AdminWorkspace
- Adjust layout: 220px drawer, 10px content padding, 64px toolbar height
* Add appsettings.Development.json to gitignore
Prevent dev-only signing keys and connection strings from being committed.
* Fix CI failures: unused Python imports and CDK synth asset path
CDK synth job needs the .NET API published first so the Lambda asset
path exists. Python lint had 3 unused imports in pdf-generate.
* Apply ruff formatting to all Lambda Python files
2026-05-17 13:06:23 -04:00
|
|
|
Email = request.Email,
|
|
|
|
|
DisplayName = request.DisplayName ?? request.Email.Split('@')[0],
|
|
|
|
|
Role = role,
|
|
|
|
|
IsActive = true,
|
|
|
|
|
CreatedAt = DateTime.UtcNow,
|
|
|
|
|
UpdatedAt = DateTime.UtcNow,
|
|
|
|
|
};
|
|
|
|
|
_db.Users.Add(user);
|
|
|
|
|
await _db.SaveChangesAsync(ct);
|
|
|
|
|
}
|
2026-05-20 17:24:01 -04:00
|
|
|
else if (user.Role != role)
|
|
|
|
|
{
|
|
|
|
|
user.Role = role;
|
|
|
|
|
user.UpdatedAt = DateTime.UtcNow;
|
|
|
|
|
await _db.SaveChangesAsync(ct);
|
|
|
|
|
}
|
Implement Phases 2-5: Frontend, AI/RAG, PDF Generation (#22)
* Fix NuGet versions and add InitialCreate EF Core migration
- Update AWSSDK.SQS and AWSSDK.SecretsManager to 3.7.500.0 (actual available versions)
- Update AWSSDK.Extensions.NETCore.Setup to 3.7.400
- Generate InitialCreate migration for PostgreSQL (all 8 entities)
- Build verified: 0 errors, 0 warnings
* Implement Dispatcher Frontend (Phase 2)
React 19 + MUI v7 + TypeScript + Vite SPA matching SHOC patterns:
Redux Toolkit (auth/ui slices), TanStack React Query, axios interceptors,
react-toastify, Cognito OAuth PKCE login flow, paginated proposal list,
new proposal form with customer autocomplete and vendor PDF upload,
read-only proposal detail with status stepper timeline.
* Add AuthController for Cognito code exchange and .env.example
Backend endpoint POST /api/auth/callback exchanges the OAuth
authorization code with Cognito's token endpoint, auto-provisions
the user in the DB, and returns the access token to the frontend.
* Implement Admin Frontend Experience (Phase 3)
Three-panel admin workspace: left reference panel (submission details,
vendor data), center editor (refined scope, inline line item table with
reorder/add/remove/pricing), right similar proposals panel (KB results
with pull-to-editor). Admin dashboard with stats cards and proposal
queue table. Approval flow with confirmation dialog, mark-as-sent,
and create-revision actions. Role-based sidebar navigation.
* Implement backend dev mode, internal API auth, and service layer enhancements
- Add dev-login endpoint with local JWT signing for local development
- Add InternalApiKeyMiddleware with timing-safe comparison for Lambda-to-API auth
- Add DevS3Service and NoOpJobPublisher for running without AWS services
- Implement CurrentUserService cascading user resolution (ID → sub → email → create)
- Add async ResolveAsync() to avoid synchronous DB calls in request pipeline
- Add /proposals/stats endpoint for efficient server-side status counts
- Guard status transitions: only allow Draft → InReview via update endpoint
- Add vendor proposals, generated PDFs, and similar proposals controllers
- Add ISimilarProposalService and SimilarProposalService
- Add [Authorize] to AddSimilarReference endpoint
* Implement Lambda functions for PDF processing, suggestions, and library ingest
- pdf-extract: Parse vendor PDFs with pdfplumber, fallback to Claude multimodal
- pdf-generate: Generate branded proposal PDFs with reportlab Platypus
- library-ingest: Format approved proposals as markdown and sync to Bedrock KB
- suggestions: Query KB for similar proposals, generate line items via Claude
- All Lambdas use internal API key auth and cold-start secret caching
- Fix pdf_path unbound variable in pdf-extract error handling
* Add Bedrock Knowledge Base, OpenSearch Serverless, and SQS message filtering
- Provision OpenSearch Serverless collection for vector search
- Create Bedrock Knowledge Base with Titan embedding model
- Configure S3 data source with fixed-size chunking (512 tokens, 20% overlap)
- Add suggestions Lambda with SQS event source filtering
- Scope bedrock:InvokeModel IAM to specific model ARN patterns
- Add internal API key secret in Secrets Manager
- Add log retention (2 months) to all Lambda functions
- Add docker-compose.yml for local PostgreSQL
* Apply SHOC design system styling across frontend
- Rewrite theme with SHOC palette (#0c4f6f primary, Nunito font, 4px radius)
- Add global CSS with Google Fonts import for Nunito
- Redesign Topbar with avatar initials, role subtitle, gradient header
- Redesign Sidebar with 220px width, section headers, active state border
- Restyle LoginPage with SHOC branded card and dev-mode role selector
- Update AdminDashboard KPI cards to centered SHOC style
- Add devLogin API method for local development auth flow
* Fix frontend navigation bugs, differentiate Dashboard from Proposals list
- Fix double nav selection by adding isNavActive() with ALL_NAV_PATHS set
- Fix /admin/users routing to placeholder instead of redirect to /
- Fix ProposalDetailPage Back button navigating to / instead of /proposals
- Differentiate Dashboard (KPI cards + recent 5) from ProposalListPage (full paginated table)
- Dashboard now uses dedicated /proposals/stats endpoint for accurate counts
- Fix adminApi.getPdf dead code (axios rejects before status check)
- Wire up PDF generation button in AdminWorkspace
- Adjust layout: 220px drawer, 10px content padding, 64px toolbar height
* Add appsettings.Development.json to gitignore
Prevent dev-only signing keys and connection strings from being committed.
* Fix CI failures: unused Python imports and CDK synth asset path
CDK synth job needs the .NET API published first so the Lambda asset
path exists. Python lint had 3 unused imports in pdf-generate.
* Apply ruff formatting to all Lambda Python files
2026-05-17 13:06:23 -04:00
|
|
|
|
|
|
|
|
var claims = new List<Claim>
|
|
|
|
|
{
|
|
|
|
|
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
|
|
|
new("sub", user.CognitoSub),
|
|
|
|
|
new("email", user.Email),
|
|
|
|
|
new("name", user.DisplayName),
|
|
|
|
|
new("cognito:groups", role.ToString().ToLower() + "s"),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey));
|
|
|
|
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
|
|
|
var token = new JwtSecurityToken(
|
|
|
|
|
issuer: "proposal-system-dev",
|
|
|
|
|
audience: "proposal-system-dev",
|
|
|
|
|
claims: claims,
|
|
|
|
|
expires: DateTime.UtcNow.AddHours(12),
|
|
|
|
|
signingCredentials: creds);
|
|
|
|
|
|
|
|
|
|
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
|
|
|
|
|
|
|
|
|
|
return Ok(new AuthResponse(
|
|
|
|
|
user.Id.ToString(),
|
|
|
|
|
user.Email,
|
|
|
|
|
user.DisplayName,
|
|
|
|
|
user.Role.ToString(),
|
|
|
|
|
tokenString
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<CognitoTokenResponse?> ExchangeCodeAsync(
|
|
|
|
|
string domain, string clientId, string code, string redirectUri, CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
var client = _httpClientFactory.CreateClient();
|
|
|
|
|
var tokenUrl = $"https://{domain}/oauth2/token";
|
|
|
|
|
|
|
|
|
|
var content = new FormUrlEncodedContent(new Dictionary<string, string>
|
|
|
|
|
{
|
|
|
|
|
["grant_type"] = "authorization_code",
|
|
|
|
|
["client_id"] = clientId,
|
|
|
|
|
["code"] = code,
|
|
|
|
|
["redirect_uri"] = redirectUri,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var response = await client.PostAsync(tokenUrl, content, ct);
|
|
|
|
|
if (!response.IsSuccessStatusCode) return null;
|
|
|
|
|
|
|
|
|
|
var json = await response.Content.ReadAsStringAsync(ct);
|
|
|
|
|
return JsonSerializer.Deserialize<CognitoTokenResponse>(json);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public record AuthCallbackRequest(string Code, string RedirectUri);
|
|
|
|
|
|
|
|
|
|
public record DevLoginRequest(string Email, string? DisplayName, string? Role);
|
|
|
|
|
|
|
|
|
|
public record AuthResponse(string Id, string Email, string DisplayName, string Role, string Token);
|
|
|
|
|
|
|
|
|
|
internal class CognitoTokenResponse
|
|
|
|
|
{
|
|
|
|
|
[System.Text.Json.Serialization.JsonPropertyName("access_token")]
|
|
|
|
|
public string AccessToken { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[System.Text.Json.Serialization.JsonPropertyName("id_token")]
|
|
|
|
|
public string IdToken { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[System.Text.Json.Serialization.JsonPropertyName("refresh_token")]
|
|
|
|
|
public string RefreshToken { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[System.Text.Json.Serialization.JsonPropertyName("token_type")]
|
|
|
|
|
public string TokenType { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
[System.Text.Json.Serialization.JsonPropertyName("expires_in")]
|
|
|
|
|
public int ExpiresIn { get; set; }
|
|
|
|
|
}
|