All checks were successful
Build and Deploy / deploy (push) Successful in 41s
Ticket replies now include full onboarding info (webmail, IMAP/SMTP, Mattermost, Gitea, SilverDESK URLs) instead of raw provisioning status. Confirmation success page uses clickable service links with email client config details. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
181 lines
8.1 KiB
C#
181 lines
8.1 KiB
C#
using SilverLabs.Website.Models;
|
|
using SilverLabs.Website.Services;
|
|
|
|
namespace SilverLabs.Website.Endpoints;
|
|
|
|
public static class DeveloperEndpoints
|
|
{
|
|
public static void MapDeveloperEndpoints(this WebApplication app)
|
|
{
|
|
var group = app.MapGroup("/api/developers");
|
|
|
|
group.MapGet("/check-username/{username}", async (string username, DeveloperApplicationService service) =>
|
|
{
|
|
var available = await service.CheckUsernameAsync(username);
|
|
if (available is null)
|
|
return Results.Problem("Unable to verify username availability", statusCode: 503);
|
|
return Results.Ok(new { available = available.Value });
|
|
});
|
|
|
|
group.MapPost("/apply", async (DeveloperApplication application, DeveloperApplicationService service) =>
|
|
{
|
|
var (success, message, token) = await service.SubmitApplicationAsync(application);
|
|
return success
|
|
? Results.Ok(new { message, token })
|
|
: Results.Problem(message, statusCode: 502);
|
|
});
|
|
|
|
group.MapPost("/approve/{ticketId}", async (
|
|
string ticketId,
|
|
DeveloperTicketParsingService ticketService,
|
|
ProvisioningService provisioningService,
|
|
HttpContext context,
|
|
IConfiguration config) =>
|
|
{
|
|
var apiKey = context.Request.Headers["X-Api-Key"].FirstOrDefault();
|
|
var expectedKey = config["AdminApiKey"];
|
|
|
|
if (string.IsNullOrEmpty(expectedKey) || apiKey != expectedKey)
|
|
return Results.Unauthorized();
|
|
|
|
var ticket = await ticketService.FetchTicketAsync(ticketId);
|
|
if (ticket is null)
|
|
return Results.Problem("Failed to fetch ticket from SilverDESK", statusCode: 502);
|
|
|
|
var description = ticket.Value.GetProperty("description").GetString() ?? "";
|
|
var (fullName, email, desiredUsername) = ticketService.ParseApplicationFromDescription(description);
|
|
|
|
if (string.IsNullOrEmpty(fullName) || string.IsNullOrEmpty(email) || string.IsNullOrEmpty(desiredUsername))
|
|
return Results.Problem("Could not parse applicant details from ticket description", statusCode: 422);
|
|
|
|
// Generate confirmation token instead of provisioning immediately
|
|
var deployment = provisioningService.CreatePendingDeployment(desiredUsername, email, fullName, ticketId);
|
|
|
|
var siteBase = config["SiteBaseUrl"] ?? "https://silverlabs.uk";
|
|
var confirmUrl = $"{siteBase}/developers/confirm/{deployment.Token}";
|
|
|
|
// Send ticket reply with confirmation link
|
|
var replyContent = $"""
|
|
Your application has been approved! To activate your accounts, please confirm your identity:
|
|
|
|
**[Click here to activate your accounts]({confirmUrl})**
|
|
|
|
You'll need to enter your SilverDESK password to complete the setup. This link expires in 48 hours.
|
|
|
|
Once confirmed, the following accounts will be created for you:
|
|
- **Email**: {desiredUsername}@silverlabs.uk
|
|
- **Mattermost**: Team chat access
|
|
- **Gitea**: Source code repository access
|
|
""";
|
|
|
|
var (replyOk, replyMsg) = await provisioningService.SendTicketReplyAsync(ticketId, replyContent);
|
|
|
|
return Results.Ok(new
|
|
{
|
|
success = true,
|
|
message = $"Confirmation link generated and sent via ticket reply. Reply status: {replyMsg}",
|
|
confirmUrl
|
|
});
|
|
});
|
|
|
|
// Token info endpoint for the confirmation page
|
|
group.MapGet("/deployment-info/{token}", (string token, ProvisioningService provisioningService) =>
|
|
{
|
|
var deployment = provisioningService.GetPendingDeployment(token);
|
|
if (deployment is null)
|
|
return Results.NotFound(new { message = "Invalid or expired confirmation link" });
|
|
|
|
return Results.Ok(new
|
|
{
|
|
username = deployment.Username,
|
|
email = deployment.Email,
|
|
fullName = deployment.FullName,
|
|
expiresAt = deployment.ExpiresAt
|
|
});
|
|
});
|
|
|
|
// Confirm deployment with password
|
|
group.MapPost("/confirm-deployment", async (
|
|
ConfirmDeploymentRequest request,
|
|
ProvisioningService provisioningService) =>
|
|
{
|
|
var deployment = provisioningService.GetPendingDeployment(request.Token);
|
|
if (deployment is null)
|
|
return Results.NotFound(new { message = "Invalid or expired confirmation link" });
|
|
|
|
// Validate credentials against SilverDESK
|
|
var authenticated = await provisioningService.ValidateSilverDeskCredentialsAsync(
|
|
deployment.Username, request.Password);
|
|
|
|
if (!authenticated)
|
|
return Results.Json(new { message = "Invalid password. Please enter your SilverDESK password." }, statusCode: 401);
|
|
|
|
// Provision all services with the user's password
|
|
var (success, message) = await provisioningService.ProvisionWithPasswordAsync(
|
|
deployment.TicketId, deployment.Username, deployment.Email, deployment.FullName, request.Password);
|
|
|
|
// Send follow-up ticket reply with results
|
|
var resultContent = success
|
|
? $"""
|
|
Your accounts have been successfully provisioned! Here's how to access your services:
|
|
|
|
**Email**: {deployment.Username}@silverlabs.uk
|
|
- Webmail: [mail.silverlined.uk](https://mail.silverlined.uk)
|
|
- IMAP: `mail.silverlined.uk:993` (SSL)
|
|
- SMTP: `mail.silverlined.uk:465` (SSL)
|
|
|
|
**Mattermost** (Team Chat): [ops.silverlined.uk](https://ops.silverlined.uk)
|
|
|
|
**Gitea** (Source Code): [git.silverlabs.uk](https://git.silverlabs.uk)
|
|
|
|
**SilverDESK** (Support & Tickets): [silverdesk.silverlabs.uk](https://silverdesk.silverlabs.uk)
|
|
|
|
All services use the same password you entered during activation.
|
|
|
|
---
|
|
*Provisioning status: {message}*
|
|
"""
|
|
: $"""
|
|
Account provisioning completed with some issues:
|
|
|
|
{message}
|
|
|
|
Some services may not be available yet. Please contact an administrator for assistance.
|
|
|
|
Once resolved, your services will be:
|
|
- **Email**: {deployment.Username}@silverlabs.uk — [mail.silverlined.uk](https://mail.silverlined.uk)
|
|
- **Mattermost**: [ops.silverlined.uk](https://ops.silverlined.uk)
|
|
- **Gitea**: [git.silverlabs.uk](https://git.silverlabs.uk)
|
|
""";
|
|
|
|
await provisioningService.SendTicketReplyAsync(deployment.TicketId, resultContent, "close");
|
|
|
|
// Remove the used token
|
|
provisioningService.RemovePendingDeployment(request.Token);
|
|
|
|
return Results.Ok(new { success, message });
|
|
});
|
|
|
|
// Password sync endpoint (called by SilverDESK on password reset)
|
|
group.MapPost("/sync-password", async (
|
|
SyncPasswordRequest request,
|
|
ProvisioningService provisioningService,
|
|
HttpContext context,
|
|
IConfiguration config) =>
|
|
{
|
|
var apiKey = context.Request.Headers["X-Api-Key"].FirstOrDefault();
|
|
var expectedKey = config["AdminApiKey"];
|
|
|
|
if (string.IsNullOrEmpty(expectedKey) || apiKey != expectedKey)
|
|
return Results.Unauthorized();
|
|
|
|
var (success, message) = await provisioningService.SyncPasswordAsync(request.Username, request.NewPassword);
|
|
|
|
return Results.Ok(new { success, message });
|
|
});
|
|
}
|
|
}
|
|
|
|
public record ConfirmDeploymentRequest(string Token, string Password);
|
|
public record SyncPasswordRequest(string Username, string NewPassword);
|