Initial commit, back end is working, front end is sending requests correctly but need to fix the state issues
This commit is contained in:
commit
ef66b9dbf9
37 changed files with 19827 additions and 0 deletions
3
Client/.eslintrc.json
Normal file
3
Client/.eslintrc.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
36
Client/.gitignore
vendored
Normal file
36
Client/.gitignore
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
9
Client/jest.config.js
Normal file
9
Client/jest.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const nextJest = require("next/jest");
|
||||
const createJestConfig = nextJest({
|
||||
dir: "./",
|
||||
});
|
||||
const customJestConfig = {
|
||||
moduleDirectories: ["node_modules", "<rootDir>/"],
|
||||
testEnvironment: "jest-environment-jsdom",
|
||||
};
|
||||
module.exports = createJestConfig(customJestConfig);
|
6
Client/next.config.mjs
Normal file
6
Client/next.config.mjs
Normal file
|
@ -0,0 +1,6 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
9111
Client/package-lock.json
generated
Normal file
9111
Client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
36
Client/package.json
Normal file
36
Client/package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "front-end",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"test": "jest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"http-proxy-middleware": "^3.0.0",
|
||||
"next": "14.2.5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"watch": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@testing-library/jest-dom": "^6.4.6",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.5",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
8
Client/postcss.config.mjs
Normal file
8
Client/postcss.config.mjs
Normal file
|
@ -0,0 +1,8 @@
|
|||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
BIN
Client/public/strive-logo.jpg
Normal file
BIN
Client/public/strive-logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
7
Client/src/pages/_app.tsx
Normal file
7
Client/src/pages/_app.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
import "@/styles/globals.css";
|
||||
import type { AppProps } from "next/app";
|
||||
import 'bootstrap-icons/font/bootstrap-icons.css';
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
13
Client/src/pages/_document.tsx
Normal file
13
Client/src/pages/_document.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Html, Head, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
157
Client/src/pages/index.tsx
Normal file
157
Client/src/pages/index.tsx
Normal file
|
@ -0,0 +1,157 @@
|
|||
'use client'
|
||||
|
||||
import Image from "next/image";
|
||||
import React, {useState} from "react";
|
||||
import {useRef} from "react";
|
||||
|
||||
export default function Home() {
|
||||
|
||||
// there's 100% a better way of handling multiple related states
|
||||
// i'd need to redo how these states function as currently, they will only
|
||||
// be considered valid when all of the checks pass AND one of the fields changes
|
||||
// meaning that the passwords won't match correctly as i am checking whether they WERE valid PRIOR to one of them updating
|
||||
const [validPasswordLength, setValidPasswordLength] = useState(false);
|
||||
const [validMinimumCharacters, setValidMinimumCharacters] = useState(false);
|
||||
const [validSpecialCharacters, setValidSpecialCharacters] = useState(false);
|
||||
const [validIdenticalPasswords, setValidIdenticalPasswords] = useState(false);
|
||||
const passwordInput = useRef(null);
|
||||
const confirmInput = useRef(null);
|
||||
|
||||
function handlePasswordFieldChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
setValidPasswordLength(validatePasswordLength(e.target.value));
|
||||
setValidMinimumCharacters(validatePasswordMinimumCharacters(e.target.value));
|
||||
setValidSpecialCharacters(validatePasswordSpecialCharacters(e.target.value));
|
||||
setValidIdenticalPasswords(validatePasswordsEqual(e.target.value));
|
||||
console.log(validPasswordLength, validMinimumCharacters, validSpecialCharacters, validIdenticalPasswords);
|
||||
}
|
||||
|
||||
function validatePasswordLength(password: string) {
|
||||
// match any characters as long as the string is between 7 and 14 characters
|
||||
const regex = new RegExp('^.{7,14}$');
|
||||
return regex.test(password);
|
||||
}
|
||||
|
||||
function validatePasswordMinimumCharacters(password: string) {
|
||||
// look ahead for the special characters and digits and match them at least once
|
||||
const regex = new RegExp('^(?=.*?[!£$^*#])(?=.*?[0-9]).*$');
|
||||
return regex.test(password);
|
||||
}
|
||||
|
||||
function validatePasswordSpecialCharacters(password: string) {
|
||||
// check for any characters not in the allowed list
|
||||
const regex = new RegExp('([^a-zA-Z0-9!£$^*#\d\s])');
|
||||
return !regex.test(password);
|
||||
}
|
||||
|
||||
function validatePasswordsEqual(password: string) {
|
||||
// this feels wrong
|
||||
return password === confirmInput.current.value;
|
||||
}
|
||||
|
||||
function isPasswordValidRegex(password: string) {
|
||||
// following regex does the following:
|
||||
// ^ -> start of word
|
||||
// (?=.*?[XXX]) -> look ahead / match any character / between zero and unlimited times/ that matches any of the characters in XXX
|
||||
// three matching groups for characters a-z lower and upper case, digits from 0-9 and for any of the valid 'special' characters
|
||||
// make sure that the characters in the password are only the allowed alphanumerics and special characters
|
||||
// {7,14} -> match the previous tokens only between 7-14 times
|
||||
// $ -> match the end of the password
|
||||
const regex = new RegExp('^(?=.*?[a-zA-Z])(?=.*?[0-9])(?=.*?[!£$^*#])[a-zA-Z0-9!£$^*#]{7,14}$');
|
||||
console.log(regex.test(password));
|
||||
return regex.test(password)
|
||||
}
|
||||
|
||||
async function handleFormSubmit(e: React.MouseEvent<HTMLButtonElement>) {
|
||||
e.preventDefault();
|
||||
if(isPasswordValidRegex(passwordInput.current.value)
|
||||
&& isPasswordValidRegex(confirmInput.current.value)
|
||||
&& (confirmInput.current.value === passwordInput.current.value)) {
|
||||
console.log("VALID PASSWORDS");
|
||||
const response = await fetch("https://localhost:7144/Password/change", {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({password: passwordInput.current.value})
|
||||
});
|
||||
console.log(response);
|
||||
}
|
||||
console.log('Post password to back end');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<Image src="/strive-logo.jpg" alt="Strive Gaming" height="110" width="110" className="mx-auto"/>
|
||||
<h2 className="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900"
|
||||
data-testid="title">
|
||||
Change your password
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<form className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="password"
|
||||
className="block text-sm font-medium leading-6 text-gray-900">
|
||||
New password
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
id="password"
|
||||
ref={passwordInput}
|
||||
name="password"
|
||||
type="password"
|
||||
data-testid="password"
|
||||
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
onChange={handlePasswordFieldChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<label htmlFor="confirm" className="block text-sm font-medium leading-6 text-gray-900">
|
||||
Re-type new password
|
||||
</label>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
id="confirm"
|
||||
ref={confirmInput}
|
||||
name="confirm"
|
||||
type="password"
|
||||
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||
onChange={handlePasswordFieldChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className={"flex w-full justify-center bg-gray-900 hover:bg-gray-700 active:bg-gray-800 px-4 py-2 rounded-md text-white"}
|
||||
onClick={(e) => handleFormSubmit(e)}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto text-xs mt-8">
|
||||
<ol>
|
||||
<li>
|
||||
Password must be between 7-14 characters in length
|
||||
</li>
|
||||
<li>
|
||||
Password must contain at least 1 number and one special characters
|
||||
</li>
|
||||
<li>
|
||||
Password does not contain special characters other than <code>!£$^*#</code>
|
||||
</li>
|
||||
<li>
|
||||
Both passwords must be identical
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
0
Client/src/styles/Home.module.css
Normal file
0
Client/src/styles/Home.module.css
Normal file
9
Client/src/styles/globals.css
Normal file
9
Client/src/styles/globals.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
}
|
22
Client/tailwind.config.ts
Normal file
22
Client/tailwind.config.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic":
|
||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
};
|
||||
export default config;
|
11
Client/tests/index.test.js
Normal file
11
Client/tests/index.test.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import Home from "../src/pages/index";
|
||||
import "@testing-library/jest-dom";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
describe("PasswordSetter", () => {
|
||||
it("renders the expected elements on the page", () => {
|
||||
render(<Home />);
|
||||
expect(screen.getByTestId("title")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("password")).toBeInTheDocument();
|
||||
});
|
||||
});
|
21
Client/tsconfig.json
Normal file
21
Client/tsconfig.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
25
Server/.dockerignore
Normal file
25
Server/.dockerignore
Normal file
|
@ -0,0 +1,25 @@
|
|||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
34
Server/.gitignore
vendored
Normal file
34
Server/.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Common IntelliJ Platform excludes
|
||||
|
||||
# User specific
|
||||
**/.idea/**/workspace.xml
|
||||
**/.idea/**/tasks.xml
|
||||
**/.idea/shelf/*
|
||||
**/.idea/dictionaries
|
||||
**/.idea/httpRequests/
|
||||
|
||||
# Sensitive or high-churn files
|
||||
**/.idea/**/dataSources/
|
||||
**/.idea/**/dataSources.ids
|
||||
**/.idea/**/dataSources.xml
|
||||
**/.idea/**/dataSources.local.xml
|
||||
**/.idea/**/sqlDataSources.xml
|
||||
**/.idea/**/dynamic.xml
|
||||
|
||||
# Rider
|
||||
# Rider auto-generates .iml files, and contentModel.xml
|
||||
**/.idea/**/*.iml
|
||||
**/.idea/**/contentModel.xml
|
||||
**/.idea/**/modules.xml
|
||||
|
||||
*.suo
|
||||
*.user
|
||||
.vs/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
_UpgradeReport_Files/
|
||||
[Pp]ackages/
|
||||
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
.DS_Store
|
13
Server/.idea/.idea.BackEnd/.idea/.gitignore
vendored
Normal file
13
Server/.idea/.idea.BackEnd/.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/.idea.BackEnd.iml
|
||||
/modules.xml
|
||||
/projectSettingsUpdater.xml
|
||||
/contentModel.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
1
Server/.idea/.idea.BackEnd/.idea/.name
Normal file
1
Server/.idea/.idea.BackEnd/.idea/.name
Normal file
|
@ -0,0 +1 @@
|
|||
BackEnd
|
4
Server/.idea/.idea.BackEnd/.idea/encodings.xml
Normal file
4
Server/.idea/.idea.BackEnd/.idea/encodings.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
8
Server/.idea/.idea.BackEnd/.idea/indexLayout.xml
Normal file
8
Server/.idea/.idea.BackEnd/.idea/indexLayout.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
22
Server/BackEnd.sln
Normal file
22
Server/BackEnd.sln
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackEnd", "BackEnd\BackEnd.csproj", "{936595E0-B2A6-42B9-84F8-AA8D52466E5E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.BackEnd", "Test.BackEnd\Test.BackEnd.csproj", "{911B1058-1BDB-46E7-8365-CB6CEC864C54}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{936595E0-B2A6-42B9-84F8-AA8D52466E5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{936595E0-B2A6-42B9-84F8-AA8D52466E5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{936595E0-B2A6-42B9-84F8-AA8D52466E5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{936595E0-B2A6-42B9-84F8-AA8D52466E5E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{911B1058-1BDB-46E7-8365-CB6CEC864C54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{911B1058-1BDB-46E7-8365-CB6CEC864C54}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{911B1058-1BDB-46E7-8365-CB6CEC864C54}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{911B1058-1BDB-46E7-8365-CB6CEC864C54}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
22
Server/BackEnd/BackEnd.csproj
Normal file
22
Server/BackEnd/BackEnd.csproj
Normal file
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>back_end</RootNamespace>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.20"/>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\.dockerignore">
|
||||
<Link>.dockerignore</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
33
Server/BackEnd/Controllers/PasswordController.cs
Normal file
33
Server/BackEnd/Controllers/PasswordController.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using back_end.Models;
|
||||
using back_end.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace back_end.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class PasswordController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<PasswordController> _logger;
|
||||
private readonly IPasswordService _passwordService;
|
||||
|
||||
public PasswordController(ILogger<PasswordController> logger, IPasswordService passwordService)
|
||||
{
|
||||
_logger = logger;
|
||||
_passwordService = passwordService;
|
||||
_passwordService.LoadCommonPasswords("Data/common-passwords.txt");
|
||||
}
|
||||
|
||||
[HttpPost("change")]
|
||||
public IActionResult SetPassword(PasswordChangeRequest request)
|
||||
{
|
||||
_logger.LogInformation("Received password change request");
|
||||
|
||||
if (_passwordService.IsPasswordInvalid(request.Password) ||
|
||||
_passwordService.IsPasswordCommon(request.Password))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
}
|
10000
Server/BackEnd/Data/common-passwords.txt
Normal file
10000
Server/BackEnd/Data/common-passwords.txt
Normal file
File diff suppressed because it is too large
Load diff
20
Server/BackEnd/Dockerfile
Normal file
20
Server/BackEnd/Dockerfile
Normal file
|
@ -0,0 +1,20 @@
|
|||
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["back-end/back-end.csproj", "back-end/"]
|
||||
RUN dotnet restore "back-end/back-end.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/back-end"
|
||||
RUN dotnet build "back-end.csproj" -c Release -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "back-end.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "back-end.dll"]
|
8
Server/BackEnd/Models/PasswordChangeRequest.cs
Normal file
8
Server/BackEnd/Models/PasswordChangeRequest.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace back_end.Models;
|
||||
|
||||
public class PasswordChangeRequest
|
||||
{
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
45
Server/BackEnd/Program.cs
Normal file
45
Server/BackEnd/Program.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using back_end.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllers();
|
||||
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
var allowedOrigin = builder.Configuration.GetSection("AllowedOrigins").Get<string[]>();
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("localApp", policy =>
|
||||
{
|
||||
policy.WithOrigins(allowedOrigin)
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
});
|
||||
|
||||
// DI
|
||||
builder.Services.AddScoped<IPasswordService, PasswordService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.UseCors("localApp");
|
||||
|
||||
app.Run();
|
41
Server/BackEnd/Properties/launchSettings.json
Normal file
41
Server/BackEnd/Properties/launchSettings.json
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:62295",
|
||||
"sslPort": 44371
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5291",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7144;http://localhost:5291",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
Server/BackEnd/Services/IPasswordService.cs
Normal file
8
Server/BackEnd/Services/IPasswordService.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace back_end.Services;
|
||||
|
||||
public interface IPasswordService
|
||||
{
|
||||
public void LoadCommonPasswords(string filepath);
|
||||
public bool IsPasswordInvalid(string password);
|
||||
public bool IsPasswordCommon(string password);
|
||||
}
|
34
Server/BackEnd/Services/PasswordService.cs
Normal file
34
Server/BackEnd/Services/PasswordService.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace back_end.Services;
|
||||
|
||||
public class PasswordService : IPasswordService
|
||||
{
|
||||
private List<string> _commonPasswords = new List<string>();
|
||||
|
||||
public void LoadCommonPasswords(string filepath)
|
||||
{
|
||||
if (_commonPasswords.Count != 0) return;
|
||||
try
|
||||
{
|
||||
_commonPasswords.AddRange(File.ReadAllLines(filepath));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("Error loading common passwords.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPasswordInvalid(string password)
|
||||
{
|
||||
// RegEx feels like cheating it's so good
|
||||
Regex regex = new Regex("^(?=.*?[a-zA-Z])(?=.*?[0-9])(?=.*?[!£$^*#])[a-zA-Z0-9!£$^*#]{7,14}$");
|
||||
return !regex.IsMatch(password);
|
||||
}
|
||||
|
||||
public bool IsPasswordCommon(string? password)
|
||||
{
|
||||
if (password == null) return true;
|
||||
return _commonPasswords.Contains(password);
|
||||
}
|
||||
}
|
11
Server/BackEnd/appsettings.Development.json
Normal file
11
Server/BackEnd/appsettings.Development.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedOrigins": [
|
||||
"http://localhost:3000"
|
||||
]
|
||||
}
|
9
Server/BackEnd/appsettings.json
Normal file
9
Server/BackEnd/appsettings.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
1
Server/Test.BackEnd/GlobalUsings.cs
Normal file
1
Server/Test.BackEnd/GlobalUsings.cs
Normal file
|
@ -0,0 +1 @@
|
|||
global using Xunit;
|
32
Server/Test.BackEnd/Test.BackEnd.csproj
Normal file
32
Server/Test.BackEnd/Test.BackEnd.csproj
Normal file
|
@ -0,0 +1,32 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.18.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.2.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BackEnd\BackEnd.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
7
Server/global.json
Normal file
7
Server/global.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "7.0.0",
|
||||
"rollForward": "latestMinor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue