🎉 Setup Pass frontend project

This commit is contained in:
2025-07-16 01:53:00 +08:00
parent 5549051ec5
commit cd4af2e26f
51 changed files with 6033 additions and 411 deletions

View File

@@ -1,110 +0,0 @@
@page "/auth/captcha"
@model DysonNetwork.Pass.Pages.Checkpoint.CheckpointPage
@{
ViewData["Title"] = "Security Checkpoint";
var cfg = ViewData.Model.Configuration;
var provider = cfg.GetSection("Captcha")["Provider"]?.ToLower();
var apiKey = cfg.GetSection("Captcha")["ApiKey"];
}
@section Scripts {
@switch (provider)
{
case "recaptcha":
<script src="https://www.recaptcha.net/recaptcha/api.js" async defer></script>
break;
case "cloudflare":
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
break;
case "hcaptcha":
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
break;
}
<script>
function getQueryParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
function onSuccess(token) {
window.parent.postMessage("captcha_tk=" + token, "*");
const redirectUri = getQueryParam("redirect_uri");
if (redirectUri) {
window.location.href = `${redirectUri}?captcha_tk=${encodeURIComponent(token)}`;
}
}
</script>
}
<div class="hero min-h-full bg-base-200">
<div class="hero-content text-center">
<div class="max-w-md">
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h1 class="card-title">Security Check</h1>
<p>Please complete the contest below to confirm you're not a robot</p>
<div class="flex justify-center my-8">
@switch (provider)
{
case "cloudflare":
<div class="cf-turnstile"
data-sitekey="@apiKey"
data-callback="onSuccess">
</div>
break;
case "recaptcha":
<div class="g-recaptcha"
data-sitekey="@apiKey"
data-callback="onSuccess">
</div>
break;
case "hcaptcha":
<div class="h-captcha"
data-sitekey="@apiKey"
data-callback="onSuccess">
</div>
break;
default:
<div class="alert alert-warning">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
<span>Captcha provider not configured correctly.</span>
</div>
break;
}
</div>
<div class="text-center text-sm">
<div class="font-semibold mb-1">Solar Network Anti-Robot</div>
<div class="text-base-content/70">
Powered by
@switch (provider)
{
case "cloudflare":
<a href="https://www.cloudflare.com/turnstile/" class="link link-hover">
Cloudflare Turnstile
</a>
break;
case "recaptcha":
<a href="https://www.google.com/recaptcha/" class="link link-hover">
Google reCaptcha
</a>
break;
default:
<span>Nothing</span>
break;
}
<br/>
Hosted by
<a href="https://github.com/Solsynth/DysonNetwork" class="link link-hover">
DysonNetwork.Pass
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,14 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace DysonNetwork.Pass.Pages.Checkpoint;
public class CheckpointPage(IConfiguration configuration) : PageModel
{
[BindProperty] public IConfiguration Configuration { get; set; } = configuration;
public ActionResult OnGet()
{
return Page();
}
}

View File

@@ -1,62 +0,0 @@
@using DysonNetwork.Pass.Auth
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"]</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@100..900&family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="~/css/styles.css" asp-append-version="true"/>
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
rel="stylesheet"
/>
@await RenderSectionAsync("Head", required: false)
</head>
<body class="h-full bg-base-200">
<header class="navbar bg-base-100/35 backdrop-blur-md shadow-xl fixed left-0 right-0 top-0 z-50 px-5">
<div class="flex-1">
<a class="btn btn-ghost text-xl">Solar Network</a>
</div>
<div class="flex-none">
<ul class="menu menu-horizontal menu-sm px-1">
@if (Context.Request.Cookies.TryGetValue(AuthConstants.CookieTokenName, out _))
{
<li class="tooltip tooltip-bottom" data-tip="Profile">
<a href="//account/profile">
<span class="material-symbols-outlined">account_circle</span>
</a>
</li>
<li class="tooltip tooltip-bottom" data-tip="Logout">
<form method="post" asp-page="/Account/Profile" asp-page-handler="Logout">
<button type="submit">
<span class="material-symbols-outlined">
logout
</span>
</button>
</form>
</li>
}
else
{
<li class="tooltip tooltip-bottom" data-tip="Login">
<a href="//auth/login"><span class="material-symbols-outlined">login</span></a>
</li>
}
</ul>
</div>
</header>
<main class="h-full pt-16">
@RenderBody()
</main>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@@ -1,91 +0,0 @@
@page "/spells/{spellWord}"
@using DysonNetwork.Pass.Account
@model DysonNetwork.Pass.Pages.Spell.MagicSpellPage
@{
ViewData["Title"] = "Magic Spell";
}
<div class="hero min-h-full bg-base-200">
<div class="hero-content text-center">
<div class="max-w-md">
<h1 class="text-5xl font-bold mb-4">Magic Spell</h1>
@if (Model.IsSuccess)
{
<div class="alert alert-success">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
<span>The spell was applied successfully!</span>
<p>Now you can close this page.</p>
</div>
}
else if (Model.CurrentSpell == null)
{
<div class="alert alert-warning">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
<span>The spell was expired or does not exist.</span>
</div>
}
else
{
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">
@System.Text.RegularExpressions.Regex.Replace(Model.CurrentSpell!.Type.ToString(), "([a-z])([A-Z])", "$1 $2")
</h2>
<p>for @@ @Model.CurrentSpell.Account?.Name</p>
<div class="text-sm opacity-80">
@if (Model.CurrentSpell.ExpiresAt.HasValue)
{
<p>Available until @Model.CurrentSpell.ExpiresAt.Value.ToDateTimeUtc().ToString("g")</p>
}
@if (Model.CurrentSpell.AffectedAt.HasValue)
{
<p>Available after @Model.CurrentSpell.AffectedAt.Value.ToDateTimeUtc().ToString("g")</p>
}
</div>
<p class="text-sm opacity-80">Would you like to apply this spell?</p>
<form method="post" class="mt-4">
<input type="hidden" asp-for="CurrentSpell!.Id"/>
@if (Model.CurrentSpell?.Type == MagicSpellType.AuthPasswordReset)
{
<div class="form-control w-full max-w-xs">
<label class="label" asp-for="NewPassword">
<span class="label-text">New Password</span>
</label>
<input type="password"
asp-for="NewPassword"
required
minlength="8"
placeholder="Your new password"
class="input input-bordered w-full max-w-xs"/>
</div>
}
<div class="card-actions justify-end mt-4">
<button type="submit" class="btn btn-primary">Apply</button>
</div>
</form>
</div>
</div>
}
<div class="mt-8 text-center text-sm">
<div class="font-semibold mb-1">Solar Network</div>
<div class="text-base-content/70">
<a href="https://solsynth.dev" class="link link-hover">
Solsynth LLC
</a>
&copy; @DateTime.Now.Year
<br/>
Powered by
<a href="https://github.com/Solsynth/DysonNetwork" class="link link-hover">
DysonNetwork.Pass
</a>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,52 +0,0 @@
using DysonNetwork.Pass.Account;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Pass.Pages.Spell;
public class MagicSpellPage(AppDatabase db, MagicSpellService spells) : PageModel
{
[BindProperty] public MagicSpell? CurrentSpell { get; set; }
[BindProperty] public string? NewPassword { get; set; }
public bool IsSuccess { get; set; }
public async Task<IActionResult> OnGetAsync(string spellWord)
{
spellWord = Uri.UnescapeDataString(spellWord);
var now = SystemClock.Instance.GetCurrentInstant();
CurrentSpell = await db.MagicSpells
.Where(e => e.Spell == spellWord)
.Where(e => e.ExpiresAt == null || now < e.ExpiresAt)
.Where(e => e.AffectedAt == null || now >= e.AffectedAt)
.Include(e => e.Account)
.FirstOrDefaultAsync();
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (CurrentSpell?.Id == null)
return Page();
var now = SystemClock.Instance.GetCurrentInstant();
var spell = await db.MagicSpells
.Where(e => e.Id == CurrentSpell.Id)
.Where(e => e.ExpiresAt == null || now < e.ExpiresAt)
.Where(e => e.AffectedAt == null || now >= e.AffectedAt)
.FirstOrDefaultAsync();
if (spell == null || spell.Type == MagicSpellType.AuthPasswordReset && string.IsNullOrWhiteSpace(NewPassword))
return Page();
if (spell.Type == MagicSpellType.AuthPasswordReset)
await spells.ApplyPasswordReset(spell, NewPassword!);
else
await spells.ApplyMagicSpell(spell);
IsSuccess = true;
return Page();
}
}

View File

@@ -1,2 +0,0 @@
@namespace DysonNetwork.Pass.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@@ -1,3 +0,0 @@
@{
Layout = "_Layout";
}