feat(developers): overhaul signup to auto-register SilverDESK accounts
All checks were successful
Build and Deploy / deploy (push) Successful in 41s

Users now pick a password and get a SilverDESK account immediately on
submit. The form includes debounced username availability checking,
password fields with validation, and a post-submit link to SilverDESK.
The approval flow no longer creates a SilverDESK user (already exists)
and only provisions Mattermost + Mailcow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 11:03:16 +00:00
parent a4d2e571d5
commit d0785e04e1
5 changed files with 181 additions and 24 deletions

View File

@@ -1,3 +1,5 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using SilverLabs.Website.Models;
@@ -15,13 +17,54 @@ public class DeveloperApplicationService
_logger = logger;
}
public async Task<(bool Success, string Message)> SubmitApplicationAsync(DeveloperApplication application)
public async Task<bool> CheckUsernameAsync(string username)
{
try
{
var ticketBody = FormatTicketBody(application);
var response = await _httpClient.GetAsync($"/api/auth/check-username/{Uri.EscapeDataString(username)}");
if (!response.IsSuccessStatusCode)
return false;
var payload = new
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
return result.TryGetProperty("available", out var available) && available.GetBoolean();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking username availability for {Username}", username);
return false;
}
}
public async Task<(bool Success, string Message, string? Token)> SubmitApplicationAsync(DeveloperApplication application)
{
try
{
// 1. Register user on SilverDESK
var registerPayload = new
{
username = application.DesiredUsername,
email = application.Email,
password = application.Password,
fullName = application.FullName
};
var registerResponse = await _httpClient.PostAsJsonAsync("/api/auth/register", registerPayload);
if (!registerResponse.IsSuccessStatusCode)
{
var errorBody = await registerResponse.Content.ReadAsStringAsync();
_logger.LogError("SilverDESK registration failed: {StatusCode} - {Body}", registerResponse.StatusCode, errorBody);
var friendlyMessage = ParseRegistrationError(errorBody);
return (false, friendlyMessage, null);
}
var authResult = await registerResponse.Content.ReadFromJsonAsync<JsonElement>();
var token = authResult.GetProperty("token").GetString();
// 2. Create ticket using the user's own JWT
var ticketBody = FormatTicketBody(application);
var ticketPayload = new
{
Subject = $"[Developer Program] {application.Role} Application - {application.FullName}",
Description = ticketBody,
@@ -29,28 +72,54 @@ public class DeveloperApplicationService
Category = "Developer Program"
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var ticketRequest = new HttpRequestMessage(HttpMethod.Post, "/api/tickets");
ticketRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
ticketRequest.Content = JsonContent.Create(ticketPayload);
var response = await _httpClient.PostAsync("/api/tickets", content);
var ticketResponse = await _httpClient.SendAsync(ticketRequest);
if (response.IsSuccessStatusCode)
if (!ticketResponse.IsSuccessStatusCode)
{
_logger.LogInformation("Developer application submitted for {Email} as {Role}", application.Email, application.Role);
return (true, "Application submitted successfully! We'll review it and get back to you soon.");
var errorBody = await ticketResponse.Content.ReadAsStringAsync();
_logger.LogError("Failed to create ticket: {StatusCode} - {Body}", ticketResponse.StatusCode, errorBody);
// User was created but ticket failed — still return success with a note
return (true, "Your account has been created, but we had trouble submitting your application ticket. Please log in to SilverDESK and create a support ticket.", token);
}
var errorBody = await response.Content.ReadAsStringAsync();
_logger.LogError("Failed to create ticket: {StatusCode} - {Body}", response.StatusCode, errorBody);
return (false, "Something went wrong submitting your application. Please try again later.");
_logger.LogInformation("Developer application submitted for {Email} as {Role} — user registered and ticket created",
application.Email, application.Role);
return (true, "Application submitted successfully! Your SilverDESK account has been created.", token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error submitting developer application for {Email}", application.Email);
return (false, "Unable to connect to the application service. Please try again later.");
return (false, "Unable to connect to the application service. Please try again later.", null);
}
}
private static string ParseRegistrationError(string errorBody)
{
try
{
var error = JsonSerializer.Deserialize<JsonElement>(errorBody);
if (error.TryGetProperty("message", out var message))
{
var msg = message.GetString() ?? "";
if (msg.Contains("Username already exists", StringComparison.OrdinalIgnoreCase))
return "That username is already taken. Please choose a different one.";
if (msg.Contains("Email already exists", StringComparison.OrdinalIgnoreCase))
return "An account with that email already exists.";
if (msg.Contains("Password", StringComparison.OrdinalIgnoreCase))
return msg;
return msg;
}
}
catch { }
return "Something went wrong creating your account. Please try again later.";
}
private static string FormatTicketBody(DeveloperApplication app)
{
var sb = new StringBuilder();

View File

@@ -20,17 +20,15 @@ public class ProvisioningService
var results = new List<string>();
var allSuccess = true;
// 1. Create SilverDESK user
var (deskOk, deskMsg) = await CreateSilverDeskUserAsync(username, email, fullName);
results.Add($"SilverDESK: {deskMsg}");
if (!deskOk) allSuccess = false;
// SilverDESK user already exists (created at application time)
// Only provision external services
// 2. Create Mattermost user
// 1. Create Mattermost user
var (mmOk, mmMsg) = await CreateMattermostUserAsync(username, email, fullName);
results.Add($"Mattermost: {mmMsg}");
if (!mmOk) allSuccess = false;
// 3. Create Mailcow mailbox
// 2. Create Mailcow mailbox
var (mailOk, mailMsg) = await CreateMailcowMailboxAsync(username, fullName);
results.Add($"Mailcow: {mailMsg}");
if (!mailOk) allSuccess = false;