- register and login working
This commit is contained in:
parent
6abc36bc9b
commit
806ad23315
@ -4,5 +4,5 @@ using System.ComponentModel.DataAnnotations;
|
||||
namespace OnlyPrompt.Backend.ApiModels.Auth
|
||||
{
|
||||
public record ApiLoginRequest(string UserNameOrEmail, string Password);
|
||||
public record ApiRegisterRequest([MaxLength(100)] string DisplayName, [MaxLength(100)][NoWhitespace] string UserName, string Email, string Password);
|
||||
public record ApiRegisterRequest([MaxLength(100)] string DisplayName, [MaxLength(100)][NoWhitespace] string? UserName, string Email, string Password);
|
||||
}
|
||||
|
||||
@ -1,10 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
|
||||
namespace OnlyPrompt.Backend.ApiModels.Validators
|
||||
{
|
||||
public class NoWhitespaceAttribute : ValidationAttribute
|
||||
{
|
||||
public override bool IsValid(object? value)
|
||||
{
|
||||
if (value is string strValue)
|
||||
{
|
||||
if (strValue.Any(c => char.IsWhiteSpace(c)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true; // If it's not a string, we consider it valid. Use [NoWhitespace] only on string properties.
|
||||
}
|
||||
|
||||
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
|
||||
{
|
||||
if(value is string strValue)
|
||||
@ -15,7 +27,7 @@ namespace OnlyPrompt.Backend.ApiModels.Validators
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
|
||||
return base.IsValid(value, validationContext);
|
||||
return ValidationResult.Success; // If it's not a string, we consider it valid. Use [NoWhitespace] only on string properties.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("register")]
|
||||
public async Task<Results<RedirectHttpResult, ValidationProblem, Ok<ApiUser>>> RegisterAsync([FromBody] ApiRegisterRequest request)
|
||||
public async Task<Results<RedirectHttpResult, ValidationProblem, Ok<ApiUser>>> RegisterAsync([FromBody] ApiRegisterRequest request, [FromQuery] string redirect = null)
|
||||
{
|
||||
var existingUser = await FindUserAsync(request.UserName, request.Email);
|
||||
if (existingUser is not null)
|
||||
@ -84,7 +84,7 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
},
|
||||
Roles = [ModelConstants.UserRole],
|
||||
PasswordHash = null,
|
||||
UserName = request.UserName,
|
||||
UserName = request.UserName ?? request.Email,
|
||||
Email = request.Email,
|
||||
IsLockoutEnabled = false,
|
||||
};
|
||||
@ -92,6 +92,9 @@ namespace OnlyPrompt.Backend.Controllers
|
||||
newUser.PasswordHash = _passwordHasher.HashPassword(newUser, request.Password);
|
||||
_db.Users.Add(newUser);
|
||||
await _db.SaveChangesAsync();
|
||||
if(string.IsNullOrEmpty(redirect) == false)
|
||||
return TypedResults.Redirect(redirect, false);
|
||||
|
||||
return TypedResults.Ok(_mapper.Map<ApiUser>(newUser));
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,11 @@ export async function sendFormAsync(form, url, method) {
|
||||
method = method || form.method || 'post';
|
||||
const data = formToObject(form);
|
||||
const response = await sendJsonAsync(url, data, method);
|
||||
if (response.ok && response.redirected) {
|
||||
window.location.href = response.url;
|
||||
return null;
|
||||
}
|
||||
|
||||
const responseText = await response.text();
|
||||
if (response.ok == false && handleValidationError(response, responseText, form)) {
|
||||
return null;
|
||||
@ -41,11 +46,6 @@ export async function sendFormAsync(form, url, method) {
|
||||
handleGenericFormError(response, responseText, form);
|
||||
return null;
|
||||
} else {
|
||||
if(response.redirected){
|
||||
window.location.href = response.url;
|
||||
return null;
|
||||
}
|
||||
|
||||
return responseText.length == 0 ? null : JSON.parse(responseText);
|
||||
}
|
||||
}
|
||||
@ -115,13 +115,25 @@ const unknownInputErrorTemplate = new Template(`
|
||||
</div>
|
||||
`);
|
||||
|
||||
function toCamelCase(str) {
|
||||
str = str.replace(/([-_][a-z])/gi, (match) => {
|
||||
return match.toUpperCase()
|
||||
.replace('-', '')
|
||||
.replace('_', '');
|
||||
});
|
||||
|
||||
str = str[0].toLowerCase() + str.substring(1);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
function handleValidationError(response, responseText, form) {
|
||||
if (response.status !== 400) return false;
|
||||
const responseObject = JSON.parse(responseText);
|
||||
const unknownInputErrors = {};
|
||||
if (responseObject.type === 'https://tools.ietf.org/html/rfc9110#section-15.5.1' && responseObject.errors) {
|
||||
for (const [field, messages] of Object.entries(responseObject.errors)) {
|
||||
const input = form.querySelector(`[name="${field}"]`);
|
||||
const input = form.querySelector(`[name="${toCamelCase(field)}"]`);
|
||||
if (input) {
|
||||
const parent = input.parentElement;
|
||||
const errorHtml = validationErrorTemplate.render(messages);
|
||||
|
||||
12
OnlyPrompt.Frontend/js/signup.js
Normal file
12
OnlyPrompt.Frontend/js/signup.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { sendFormAsync } from "./shared.js";
|
||||
|
||||
async function signupAsync(params) {
|
||||
const form = document.getElementById('signupForm');
|
||||
await sendFormAsync(form);
|
||||
}
|
||||
|
||||
const signupForm = document.getElementById('signupForm');
|
||||
signupForm.addEventListener('submit', async (event) => {
|
||||
event.preventDefault(); // Prevent the default form submission
|
||||
await signupAsync();
|
||||
});
|
||||
@ -63,12 +63,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="login-button">Log In</button>
|
||||
<button type="submit" id="login-button" class="login-button">Log In</button>
|
||||
</form>
|
||||
|
||||
<p class="signup-text">
|
||||
Don't have an account?
|
||||
<a href="#">Sign Up</a>
|
||||
<a href="/signup">Sign Up</a>
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
<html lang="en">
|
||||
|
||||
<!--Info about page but not visible-->
|
||||
<head>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!-- For responsive design: adapts width for different devices -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@ -19,8 +20,8 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Main container for the login page (CSS layout) -->
|
||||
<main class="login-page">
|
||||
<!-- Main container for the login page (CSS layout) -->
|
||||
<main class="login-page">
|
||||
<!-- White login card -->
|
||||
<section class="login-card">
|
||||
<!-- Logo container -->
|
||||
@ -30,69 +31,39 @@
|
||||
|
||||
<h1 class="login-title">Sign Up</h1>
|
||||
<p class="login-subtitle">Create your account to get started.</p>
|
||||
|
||||
|
||||
<!-- Login form, id is used for JavaScript validation -->
|
||||
<form id="loginForm" class="login-form">
|
||||
|
||||
<form id="signupForm" class="login-form" action="/api/v1/auth/register?redirect=/login" method="post">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="yourname@email.com"
|
||||
required
|
||||
>
|
||||
<input type="email" id="email" name="email" placeholder="yourname@email.com" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="displayName">Display Name (how it will appear to others)</label>
|
||||
<input type="text" id="displayName" name="displayName" placeholder="Enter your display name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<!-- Password field with button to show/hide password -->
|
||||
<div class="password-wrapper">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Enter your password"
|
||||
required
|
||||
>
|
||||
<input type="password" id="password" name="password" placeholder="Enter your password" required>
|
||||
<button type="button" id="togglePassword" class="toggle-password">
|
||||
Show <!-- Click to show/hide password -->
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Full Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="fullName"
|
||||
name="fullName"
|
||||
placeholder="Enter your full name"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="Enter your username"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<p class="signup-terms">
|
||||
By signing up, you agree to our
|
||||
<a href="#">Terms</a>,
|
||||
<a href="#">Privacy Policy</a> and
|
||||
<a href="#">Cookies Policy</a>.
|
||||
By signing up, you agree to our
|
||||
<a href="#">Terms</a>,
|
||||
<a href="#">Privacy Policy</a> and
|
||||
<a href="#">Cookies Policy</a>.
|
||||
</p>
|
||||
|
||||
|
||||
<button type="submit" class="login-button">Sign Up</button>
|
||||
<button type="submit" id="signup-button" class="login-button">Sign Up</button>
|
||||
</form>
|
||||
|
||||
<p class="signup-text">
|
||||
@ -102,7 +73,8 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Script for login logic and form validation -->
|
||||
<script src="../js/login.js"></script>
|
||||
<!-- Script for signup logic and form validation -->
|
||||
<script type="module" src="js/signup.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -1,17 +1,17 @@
|
||||
services:
|
||||
onlyprompt.backend:
|
||||
image: ${DOCKER_REGISTRY-}onlyprompt
|
||||
build:
|
||||
context: .
|
||||
dockerfile: OnlyPrompt.Backend/Dockerfile
|
||||
ports:
|
||||
- "${PORT_PREFIX}1:8080"
|
||||
- "${PORT_PREFIX}2:8081"
|
||||
environment:
|
||||
JWT__ISSUER: "https://onlyprompt.com"
|
||||
CONNECTIONSTRINGS__DEFAULT: "Include Error Detail=true;User ID=${DB_USER};Password=${DB_PASSWORD};Host=postgres;Port=5432;Database=${DB_NAME};Pooling=true;MinPoolSize=0;MaxPoolSize=100;Connection Lifetime=0;"
|
||||
ASPNETCORE_URLS: "http://*:8080"
|
||||
ASPNETCORE_ENVIRONMENT: "Development"
|
||||
image: ${DOCKER_REGISTRY-}onlypromptbackend
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
dockerfile: OnlyPrompt.Backend/Dockerfile
|
||||
ports:
|
||||
- "${PORT_PREFIX}1:8080"
|
||||
- "${PORT_PREFIX}2:8081"
|
||||
environment:
|
||||
JWT__ISSUER: "https://onlyprompt.com"
|
||||
CONNECTIONSTRINGS__DEFAULTCONNECTION: "Include Error Detail=true;User ID=${DB_USER};Password=${DB_PASSWORD};Host=database;Port=5432;Database=${DB_NAME};Pooling=true;MinPoolSize=0;MaxPoolSize=100;Connection Lifetime=0;"
|
||||
ASPNETCORE_URLS: "http://*:8080"
|
||||
|
||||
database:
|
||||
image: postgres:latest
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user