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