Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10868018fb | |||
| 8ce7df46c2 | |||
| f4aa59f2df | |||
| ea542ca0a7 | |||
| 3b169f18d9 | |||
| b8634dbc87 | |||
| d33edd41cf | |||
| f9cfd204bf | |||
| d5e7b8b95d | |||
| 9f24f8453e | |||
| d385f21f43 | |||
| 75fb6592f6 | |||
| 34e4328050 | |||
| bd4b4bc837 | |||
| 18b58b9b5d |
@@ -14,3 +14,4 @@ csharp_new_line_before_open_brace = none
|
||||
csharp_new_line_before_catch = false
|
||||
csharp_new_line_before_finally = false
|
||||
csharp_new_line_after_else = false
|
||||
csharp_new_line_before_else = false
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Docker Build and Release Upload
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: alpine-linux
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: build and push database
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform=linux/amd64,linux/arm64 \
|
||||
-t docker.mistox.net/mistox-sql \
|
||||
--push \
|
||||
./database
|
||||
|
||||
- name: build and push server
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform=linux/amd64,linux/arm64 \
|
||||
--build-arg BASE_URL=https://mistox.com \
|
||||
-t docker.mistox.net/mistox-website \
|
||||
--push \
|
||||
.
|
||||
Vendored
+1
@@ -22,6 +22,7 @@
|
||||
},
|
||||
"args": [
|
||||
"build",
|
||||
"--configuration=development",
|
||||
"--base-href=http://localhost:5000"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
|
||||
+11
-3
@@ -2,7 +2,7 @@
|
||||
## Build Frontend ##
|
||||
######################
|
||||
|
||||
FROM node:alpine AS build-frontend
|
||||
FROM --platform=$BUILDPLATFORM node:alpine AS build-frontend
|
||||
WORKDIR /src
|
||||
|
||||
# Define base address
|
||||
@@ -27,7 +27,7 @@ RUN ng build --base-href=${BASE_URL}
|
||||
## Build Backend ##
|
||||
#####################
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-backend
|
||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build-backend
|
||||
WORKDIR /src
|
||||
|
||||
# Copy the csproj
|
||||
@@ -39,8 +39,16 @@ RUN dotnet restore './MistoxWebsite.Server.csproj'
|
||||
# Copy the rest of the backend over
|
||||
COPY ./src/MistoxWebsite.Server/ ./
|
||||
|
||||
# Get the target arch
|
||||
ARG TARGETARCH
|
||||
|
||||
# Build the source
|
||||
RUN dotnet publish './MistoxWebsite.Server.csproj' -c Release -o /app/publish
|
||||
RUN set -e && \
|
||||
if [ "$TARGETARCH" = "arm64" ]; then RID="linux-arm64"; \
|
||||
elif [ "$TARGETARCH" = "amd64" ]; then RID="linux-x64"; \
|
||||
else echo "Unsupported ARCH: $TARGETARCH"; exit 1; \
|
||||
fi && \
|
||||
dotnet publish './MistoxWebsite.Server.csproj' -c Release -r ${RID} -o /app/publish
|
||||
|
||||
################
|
||||
## Publish ##
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
CREATE DATABASE IF NOT EXISTS `mistox`;
|
||||
USE `mistox`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `Account` (
|
||||
`ID` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`UserName` varchar(60) DEFAULT NULL,
|
||||
`Email` varchar(60) DEFAULT NULL,
|
||||
`EmailVerified` tinyint(4) DEFAULT NULL,
|
||||
`PasswordHash` varchar(100) DEFAULT NULL,
|
||||
`FailedPasswordLock` tinyint(4) DEFAULT NULL,
|
||||
`PasswordAttempts` int(11) DEFAULT NULL,
|
||||
`CurrentPasswordAttempts` int(11) DEFAULT NULL,
|
||||
`Role` varchar(45) DEFAULT NULL,
|
||||
`EmailToken` varchar(45) DEFAULT NULL,
|
||||
PRIMARY KEY (`ID`)
|
||||
) AUTO_INCREMENT=1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `Product` (
|
||||
`ID` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`Name` varchar(45) DEFAULT NULL,
|
||||
@@ -61,28 +47,3 @@ CREATE TABLE IF NOT EXISTS `Receipt` (
|
||||
`TotalCost` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`AccountID`,`ProductID`,`ReceiptID`)
|
||||
);
|
||||
|
||||
INSERT INTO Account (
|
||||
ID,
|
||||
UserName,
|
||||
Email,
|
||||
EmailVerified,
|
||||
PasswordHash,
|
||||
FailedPasswordLock,
|
||||
PasswordAttempts,
|
||||
CurrentPasswordAttempts,
|
||||
Role,
|
||||
EmailToken
|
||||
) VALUES (
|
||||
1,
|
||||
'admin',
|
||||
'admin@mistox.com',
|
||||
1,
|
||||
'$2a$11$0UeWLLqTXe3FG161QVuI0OQJ9rulspUpMG581DI6KSzDXBbFKd00S',
|
||||
1,
|
||||
1,
|
||||
5,
|
||||
0,
|
||||
'Admin',
|
||||
''
|
||||
);
|
||||
+2
-2
@@ -2,7 +2,7 @@ services:
|
||||
|
||||
mistox-server:
|
||||
container_name: mistox_server
|
||||
image: mistox-website:latest
|
||||
image: docker.mistox.net/mistox-website:latest
|
||||
restart: always
|
||||
environment:
|
||||
- PaymentService=${Payment_Service}
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
|
||||
mistox-database:
|
||||
container_name: mistox_database
|
||||
image: mistox-sql:latest
|
||||
image: docker.mistox.net/mistox-sql:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ./data:/var/lib/mysql
|
||||
|
||||
@@ -41,13 +41,18 @@
|
||||
</div>
|
||||
<!-- Login Stuff -->
|
||||
<div class="nav-login">
|
||||
<div *ngIf="auth.isLoggedIn">
|
||||
<a class="nav-login-button" href="/account/settings"><span>{{ auth.loggedInUser.userName }}</span></a>
|
||||
<a class="nav-login-button" href="/account/logout"><span>Logout</span></a>
|
||||
<div *ngIf="auth.isLoggedIn" class="top-bar-buttons flex-right">
|
||||
<a class="nav-login-button" href="https://auth.mistox.com/"><span>{{ auth.loggedInUser.userName.toUpperCase() }}</span></a>
|
||||
<a class="nav-login-button" href="/api/account/logout"><span>LOGOUT</span></a>
|
||||
</div>
|
||||
<div *ngIf="!auth.isLoggedIn">
|
||||
<a class="nav-login-button" href="/account/login"><span>Login</span></a>
|
||||
<a class="nav-login-button" href="/account/register"><span>Register</span></a>
|
||||
<div *ngIf="!auth.isLoggedIn" class="top-bar-buttons flex-right">
|
||||
|
||||
<!-- Testing Login -->
|
||||
<a *ngIf="devMode" class="nav-login-button" href="https://auth.mistox.com/account/login?returnURL=http://localhost:5000/"><span>Testing Login</span></a>
|
||||
<!-- Testing Login -->
|
||||
|
||||
<a class="nav-login-button" href="https://auth.mistox.com/account/login?returnURL=https://mistox.com/"><span>LOGIN</span></a>
|
||||
<a class="nav-login-button" href="https://auth.mistox.com/account/register?returnURL=https://mistox.com/"><span>REGISTER</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1,28 +1,11 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { ForgotPasswordComponent } from './pages/account/forgotpassword/forgotpassword.component';
|
||||
import { LoginComponent } from './pages/account/login/login.component';
|
||||
import { RegisterComponent } from './pages/account/register/register.component';
|
||||
import { MistComponent } from './pages/project/mist/mist.component';
|
||||
import { CatalogComponent } from './pages/store/catalog/catalog.component';
|
||||
import { AboutComponent } from './pages/legal/about/about.component';
|
||||
import { SettingsComponent } from './pages/account/settings/settings.component';
|
||||
import { LogoutComponent } from './pages/account/logout/logout.component';
|
||||
import { ResetPasswordComponent } from './pages/account/resetpassword/resetpassword.component';
|
||||
import { VerifyEmailComponent } from './pages/account/verifyemail/verifyemail.component';
|
||||
import { NewItemComponent } from './pages/store/admin/newitem/new.component';
|
||||
import { EditItemComponent } from './pages/store/admin/edititem/edit.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
|
||||
// Account stuff
|
||||
{ path: "account/forgotpassword", component: ForgotPasswordComponent },
|
||||
{ path: "account/resetpassword", component: ResetPasswordComponent },
|
||||
{ path: "account/verifyemail", component: VerifyEmailComponent },
|
||||
{ path: "account/login", component: LoginComponent },
|
||||
{ path: "account/logout", component: LogoutComponent },
|
||||
{ path: "account/register", component: RegisterComponent },
|
||||
{ path: "account/settings", component: SettingsComponent },
|
||||
|
||||
// Projects
|
||||
{ path: "project/mist", component: MistComponent },
|
||||
|
||||
@@ -33,6 +16,4 @@ export const routes: Routes = [
|
||||
{ path: "store/admin/new", component: NewItemComponent },
|
||||
{ path: "store/admin/edit", component: EditItemComponent },
|
||||
|
||||
// Legal
|
||||
{ path: "about", component: AboutComponent },
|
||||
]
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Router, RouterOutlet } from '@angular/router';
|
||||
import { Component, ElementRef, isDevMode, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
|
||||
import { Authentication } from './services/Authentication';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CommonModule, Location } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -11,12 +12,38 @@ import { CommonModule } from '@angular/common';
|
||||
})
|
||||
export class App {
|
||||
|
||||
devMode: boolean = false;
|
||||
|
||||
@ViewChild('homeLink') homeLink!: ElementRef<HTMLAnchorElement>;
|
||||
@ViewChild('mistLink') mistLink!: ElementRef<HTMLAnchorElement>;
|
||||
@ViewChild('storeLink') storeLink!: ElementRef<HTMLAnchorElement>;
|
||||
@ViewChild('aboutLink') aboutLink!: ElementRef<HTMLAnchorElement>;
|
||||
|
||||
constructor(public auth: Authentication, private router: Router){}
|
||||
constructor( private http: HttpClient, public auth: Authentication, private router: Router, private route: ActivatedRoute, private location: Location){
|
||||
this.devMode = isDevMode();
|
||||
|
||||
this.route.queryParams.subscribe(params => {
|
||||
const loginToken = params['LoginToken'];
|
||||
console.log("LoginToken : " + loginToken);
|
||||
if (loginToken){
|
||||
this.http.post( "api/account/loginticket", JSON.stringify(loginToken), { headers: {'Content-Type': 'application/json'} } ).subscribe({
|
||||
next: data => {
|
||||
auth.getLoginState();
|
||||
const pathWithoutQuery = this.location.path().split('?')[0];
|
||||
this.location.replaceState(pathWithoutQuery);
|
||||
},
|
||||
error: err => {
|
||||
auth.getLoginState();
|
||||
const pathWithoutQuery = this.location.path().split('?')[0];
|
||||
this.location.replaceState(pathWithoutQuery);
|
||||
}
|
||||
})
|
||||
}else{
|
||||
auth.getLoginState();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngAfterViewInit(){
|
||||
let ViewLinks = [ this.homeLink, this.mistLink, this.storeLink, this.aboutLink ];
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { WebSiteData } from "./WebsiteData";
|
||||
|
||||
export class Account {
|
||||
public id: number = -1;
|
||||
public id: number | null = null;
|
||||
public userName: string = "";
|
||||
public email: string = "";
|
||||
public emailVerified: boolean = false;
|
||||
public passwordHash: string = "";
|
||||
public siteData: WebSiteData = new WebSiteData();
|
||||
public error: string = "";
|
||||
|
||||
constructor(init?: Partial<Account>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
public role: string = "";
|
||||
public dataServer: string = "";
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export class Product {
|
||||
public id: number = -1;
|
||||
public id: number | null = null;
|
||||
public name: string = "";
|
||||
public description: string = "";
|
||||
public curShowingIMG: number = 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export class WebSiteData {
|
||||
public accountID: number = -1;
|
||||
public accountID: number | null = null;
|
||||
public failedPasswordLock: boolean = false;
|
||||
public passwordAttempts: number = 5;
|
||||
public currentPasswordAttempts: number = 0;
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
<div class="center">
|
||||
<form class="big-frame background-border" #accountForm="ngForm" (ngSubmit)="onSubmit()">
|
||||
|
||||
<h3>Forgot Password</h3>
|
||||
|
||||
<div class="frame-item">
|
||||
<input type="text" [(ngModel)]="email" name="email" placeholder=" " />
|
||||
<label>Email</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex-row">
|
||||
<div class="frame-button">
|
||||
<input class="submit" type="submit" value="Send Code" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul *ngIf="errorMsgs.length > 0" >
|
||||
<li *ngFor="let msg of errorMsgs" >{{ msg }}</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'account-forgot',
|
||||
templateUrl: './forgotpassword.component.html',
|
||||
imports: [ FormsModule, CommonModule ]
|
||||
})
|
||||
export class ForgotPasswordComponent {
|
||||
email: string = "";
|
||||
errorMsgs: string[] = [];
|
||||
returnURL: string = '/';
|
||||
|
||||
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title) {
|
||||
this.title.setTitle("Forgot Password | Mistox");
|
||||
this.route.queryParams.subscribe(params => {
|
||||
this.returnURL = params['returnURL'] || '/';
|
||||
});
|
||||
}
|
||||
|
||||
sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
// Clear errors
|
||||
this.errorMsgs = [];
|
||||
|
||||
// Send to server and wait for response
|
||||
this.errorMsgs.push("Waiting for response from server");
|
||||
const body = new HttpParams()
|
||||
.set("Email", this.email)
|
||||
const headers = new HttpHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
});
|
||||
this.http.post( "api/account/sendresetpassword", body, { headers, responseType: "text" } ).subscribe({
|
||||
next: async (data) => {
|
||||
if (data.trim() == "Success"){
|
||||
this.errorMsgs = ["Reset-password sent"];
|
||||
await this.sleep(3000);
|
||||
this.router.navigate([this.returnURL]);
|
||||
}else{
|
||||
this.errorMsgs = [data];
|
||||
}
|
||||
},
|
||||
error: err => {
|
||||
console.log("HTTP Error Signing In: ", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<div class="center">
|
||||
<form class="big-frame background-border" #accountForm="ngForm" (ngSubmit)="onSubmit()">
|
||||
<h3>Login</h3>
|
||||
|
||||
<div class="frame-item">
|
||||
<input type="text" [(ngModel)]="UserName" name="userName" placeholder=" " autocomplete="username" />
|
||||
<label>UserName</label>
|
||||
</div>
|
||||
|
||||
<div class="frame-item">
|
||||
<input type="password" [(ngModel)]="Password" name="password" placeholder=" " autocomplete="current-password" />
|
||||
<label>Password</label>
|
||||
</div>
|
||||
|
||||
<div class="flex-row">
|
||||
<div class="frame-button">
|
||||
<input class="submit" type="submit" value="LOGIN" />
|
||||
</div>
|
||||
|
||||
<div class="frame-forgot">
|
||||
<div class="sub-frame">
|
||||
Stay Logged In
|
||||
<input type="checkbox" [(ngModel)]="StayLoggedIn" name="stayLoggedIn" />
|
||||
</div>
|
||||
<div class="sub-frame">
|
||||
<a href="/account/forgotpassword">Forgot Password</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul *ngIf="errorMsgs.length > 0" >
|
||||
<li *ngFor="let msg of errorMsgs" >{{ msg }}</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,57 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Authentication, SessionType } from '../../../services/Authentication';
|
||||
|
||||
@Component({
|
||||
selector: 'account-login',
|
||||
templateUrl: './login.component.html',
|
||||
imports: [ FormsModule, CommonModule ],
|
||||
standalone: true
|
||||
})
|
||||
export class LoginComponent {
|
||||
UserName: string = "";
|
||||
Password: string = "";
|
||||
StayLoggedIn: boolean = false;
|
||||
errorMsgs: string[] = [];
|
||||
returnURL: string = '/';
|
||||
|
||||
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
|
||||
this.title.setTitle("Login | Mistox");
|
||||
this.route.queryParams.subscribe(params => {
|
||||
this.returnURL = params['returnURL'] || '/';
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.errorMsgs = [];
|
||||
|
||||
if (!this.UserName) {
|
||||
this.errorMsgs.push("The 'username' field is required");
|
||||
}
|
||||
if (!this.Password) {
|
||||
this.errorMsgs.push("The 'password' field is required");
|
||||
}
|
||||
if (this.Password.length < 6) {
|
||||
this.errorMsgs.push("Password must be at least 6 Characters long");
|
||||
}
|
||||
if (this.errorMsgs.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.errorMsgs.push("Waiting for response from server");
|
||||
this.auth.Login(this.UserName, this.Password, this.StayLoggedIn).subscribe(
|
||||
data => {
|
||||
if (data.error.length === 0){
|
||||
this.router.navigate([this.returnURL]);
|
||||
}else{
|
||||
this.errorMsgs.pop();
|
||||
this.errorMsgs.push(data.error);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Authentication } from '../../../services/Authentication';
|
||||
|
||||
@Component({
|
||||
selector: 'account-logout',
|
||||
templateUrl: './logout.component.html',
|
||||
imports: [ FormsModule, CommonModule ],
|
||||
standalone: true
|
||||
})
|
||||
export class LogoutComponent {
|
||||
errorMsgs: string[] = [];
|
||||
returnURL: string = '/';
|
||||
|
||||
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
|
||||
this.title.setTitle("Logout | Mistox");
|
||||
}
|
||||
|
||||
ngAfterViewInit(){
|
||||
this.auth.Logout();
|
||||
this.router.navigate(["/"]);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<div class="center">
|
||||
<form class="big-frame background-border" #accountForm="ngForm" (ngSubmit)="onSubmit()">
|
||||
<h3>Register</h3>
|
||||
|
||||
<div class="frame-item">
|
||||
<input type="text" [(ngModel)]="userName" name="userName" placeholder=" " autocomplete="username" />
|
||||
<label>UserName</label>
|
||||
</div>
|
||||
|
||||
<div class="frame-item">
|
||||
<input type="email" [(ngModel)]="email" name="email" placeholder=" " autocomplete="current-password" />
|
||||
<label>Email</label>
|
||||
</div>
|
||||
|
||||
<div class="frame-item">
|
||||
<input type="password" [(ngModel)]="passwordHash" name="password" placeholder=" " autocomplete="current-password" />
|
||||
<label>Password</label>
|
||||
</div>
|
||||
|
||||
<div class="frame-item">
|
||||
<input type="password" [(ngModel)]="passwordHash2" name="repeat password" placeholder=" " autocomplete="current-password" />
|
||||
<label>Repeat Password</label>
|
||||
</div>
|
||||
|
||||
<div class="flex-row">
|
||||
<div class="frame-button">
|
||||
<input class="submit" type="submit" value="REGISTER" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul *ngIf="errorMsgs.length > 0" >
|
||||
<li *ngFor="let msg of errorMsgs" >{{ msg }}</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,85 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Account } from '../../../models/Account';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'account-register',
|
||||
templateUrl: './register.component.html',
|
||||
imports: [ FormsModule, CommonModule ]
|
||||
})
|
||||
export class RegisterComponent {
|
||||
userName: string = ""
|
||||
email: string = "";
|
||||
passwordHash: string = "";
|
||||
passwordHash2: string = "";
|
||||
error: string = "";
|
||||
|
||||
errorMsgs: string[] = [];
|
||||
returnURL: string = '/';
|
||||
|
||||
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title ) {
|
||||
this.title.setTitle("Register | Mistox");
|
||||
this.route.queryParams.subscribe(params => {
|
||||
this.returnURL = params['returnURL'] || '/';
|
||||
});
|
||||
}
|
||||
|
||||
sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
// Clear errors
|
||||
this.errorMsgs = [];
|
||||
|
||||
// Validate data
|
||||
const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
if (!regex.test(this.email)){
|
||||
this.errorMsgs.push("A valid email is required");
|
||||
}
|
||||
if (!this.userName) {
|
||||
this.errorMsgs.push("The 'username' field is required");
|
||||
}
|
||||
if (!this.passwordHash) {
|
||||
this.errorMsgs.push("The 'password' field is required");
|
||||
}
|
||||
if (this.passwordHash.length < 6) {
|
||||
this.errorMsgs.push("Password must be at least 6 Characters long");
|
||||
}
|
||||
if (this.passwordHash !== this.passwordHash2){
|
||||
this.errorMsgs.push("Passwords don't match");
|
||||
}
|
||||
if (this.errorMsgs.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send to server and wait for response
|
||||
this.errorMsgs.push("Waiting for response from server");
|
||||
const body = new HttpParams()
|
||||
.set("Email", this.email)
|
||||
.set("UserName", this.userName)
|
||||
.set("PasswordHash", this.passwordHash);
|
||||
const headers = new HttpHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
});
|
||||
this.http.post<Account>( "api/account/register", body, { headers } ).subscribe({
|
||||
next: async (data) => {
|
||||
if (data.error.length === 0){
|
||||
this.errorMsgs = ["Account Created"];
|
||||
await this.sleep(3000);
|
||||
this.router.navigate([this.returnURL]);
|
||||
}else{
|
||||
this.errorMsgs = [];
|
||||
this.errorMsgs.push(data.error);
|
||||
}
|
||||
},
|
||||
error: err => {
|
||||
console.log("HTTP Error Signing In: ", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
<div class="center">
|
||||
<form class="big-frame background-border" #accountForm="ngForm" (ngSubmit)="onSubmit()">
|
||||
|
||||
<h3>Reset Password</h3>
|
||||
<h2>User: {{ UserName }}</h2>
|
||||
|
||||
<div class="frame-item">
|
||||
<input type="password" [(ngModel)]="Password" name="Password" placeholder=" " />
|
||||
<label>New Password</label>
|
||||
</div>
|
||||
|
||||
<div class="frame-item">
|
||||
<input type="password" [(ngModel)]="PassworR" name="PassworR" placeholder=" " />
|
||||
<label>Repeat New Password</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex-row">
|
||||
<div class="frame-button">
|
||||
<input class="submit" type="submit" value="Send Code" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul *ngIf="errorMsgs.length > 0" >
|
||||
<li *ngFor="let msg of errorMsgs" >{{ msg }}</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'account-reset',
|
||||
templateUrl: './resetpassword.component.html',
|
||||
imports: [ FormsModule, CommonModule ]
|
||||
})
|
||||
export class ResetPasswordComponent {
|
||||
|
||||
UserName: string = "";
|
||||
ResetPwd: string = "";
|
||||
Password: string = "";
|
||||
PassworR: string = "";
|
||||
|
||||
errorMsgs: string[] = [];
|
||||
|
||||
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title ) {
|
||||
this.title.setTitle("Reset Password | Mistox");
|
||||
this.route.queryParams.subscribe(params => {
|
||||
this.UserName = params['UserName'] || '';
|
||||
this.ResetPwd = params['ResetPwd'] || '';
|
||||
});
|
||||
}
|
||||
|
||||
sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.Password != this.PassworR){
|
||||
this.errorMsgs.push("Passwords must match");
|
||||
}
|
||||
if (this.Password.length < 6){
|
||||
this.errorMsgs.push("Password must be at least 6 Characters long");
|
||||
}
|
||||
if (this.errorMsgs.length == 0){
|
||||
// Send to server and wait for response
|
||||
this.errorMsgs.push("Waiting for response from server");
|
||||
const body = new HttpParams()
|
||||
.set("UserName", this.UserName)
|
||||
.set("NewPassword", this.Password)
|
||||
.set("ResetToken", this.ResetPwd);
|
||||
const headers = new HttpHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
});
|
||||
this.http.post<boolean>( "api/account/resetpassword", body, { headers } ).subscribe({
|
||||
next: async (data) => {
|
||||
if (data == true){
|
||||
this.errorMsgs = ["Password reset successfully"];
|
||||
await this.sleep(3000);
|
||||
this.router.navigate(["/account/login"]);
|
||||
}else{
|
||||
this.errorMsgs = ["An error has ocurred"];
|
||||
await this.sleep(3000);
|
||||
this.router.navigate(["/account/sendresetpassword"]);
|
||||
}
|
||||
},
|
||||
error: err => {
|
||||
console.log("HTTP Error Signing In: ", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Account } from '../../../models/Account';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'account-settings',
|
||||
templateUrl: './settings.component.html',
|
||||
imports: [ FormsModule, CommonModule ]
|
||||
})
|
||||
export class SettingsComponent {
|
||||
user!: Account;
|
||||
errorMsgs: string[] = [];
|
||||
returnURL: string = '/';
|
||||
|
||||
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title ) {
|
||||
this.title.setTitle("Settings | Mistox");
|
||||
this.route.queryParams.subscribe(params => {
|
||||
this.returnURL = params['returnURL'] || '/';
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<div class="center">
|
||||
<form class="big-frame background-border" #accountForm="ngForm" (ngSubmit)="onSubmit()">
|
||||
|
||||
<h3>Verifying Email</h3>
|
||||
<h3 style="color: red;">{{ Result }}</h3>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,54 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'account-verifyemail',
|
||||
templateUrl: './verifyemail.component.html',
|
||||
imports: [ FormsModule, CommonModule ]
|
||||
})
|
||||
export class VerifyEmailComponent {
|
||||
|
||||
UserName: string = "";
|
||||
Guid: string = "";
|
||||
Result: string = "";
|
||||
|
||||
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title ) {
|
||||
this.title.setTitle("Verify Email | Mistox");
|
||||
this.route.queryParams.subscribe(params => {
|
||||
this.UserName = params['UserName'] || '';
|
||||
this.Guid = params['Guid'] || '';
|
||||
});
|
||||
}
|
||||
|
||||
sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async onSubmit() {
|
||||
// Send to server and wait for response
|
||||
const body = new HttpParams()
|
||||
.set("UserName", this.UserName)
|
||||
.set("EmailToken", this.Guid);
|
||||
const headers = new HttpHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
});
|
||||
this.http.post<boolean>( "api/account/verifyemail", body, { headers } ).subscribe({
|
||||
next: async (data) => {
|
||||
if (data == true){
|
||||
this.Result = "Verified Email Successfully";
|
||||
}else{
|
||||
this.Result = "Email was not able to be verified please resend email";
|
||||
}
|
||||
await this.sleep(3000);
|
||||
this.router.navigate(["/"]);
|
||||
},
|
||||
error: err => {
|
||||
console.log("HTTP Error Signing In: ", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<div class="center">
|
||||
<div class="big-frame background-border text-frame">
|
||||
<p>Welcome to Mistox LLC. A project and hobby of Derek Holloway.</p>
|
||||
<br />
|
||||
<p>I am an indi-developer who has been making small projects since I was 13. I originally learned lua and spent 4 years mastering it. Then I moved onto C# which is my preferred language</p>
|
||||
<p>My programming catalog consist of C#, Lua, SQL, C++, C, and JavaScript in the order of knowledge from best to passiable.</p>
|
||||
<p>Im currently in college for computer sciences and should honestly be doing that instead of this but I find working on this website and hobby games to be way more enjoyable.</p>
|
||||
<br />
|
||||
<p>I would love to learn how to use Blender in order to make all the models for my games but with the amount of work ive already made for myself im going to hold off for now.</p>
|
||||
<p>This website and everything on it are the long countless hours of my time and motivation to create something that I can be proud of and share that with the world.</p>
|
||||
<p>So if you would like to support me as a small creator please feel free to leave a donation from on the store page. It would means a lot to me.</p>
|
||||
<br />
|
||||
<p>For the nerds out there, this website is a blazor webassembly app, hosted on an ubuntu webserver, with a mysql backend.</p>
|
||||
<p>All the passwords are encrypted using bcrypt for your safety and all the data is only allowed through SSL.</p>
|
||||
<p>After you make your account. All the data in the database is easily accessable through the account settings and</p>
|
||||
<p>you can delete your account at any time. Including all your data with it so there is no risk.</p>
|
||||
<p>I wont show ads and never will and I refuse to use trackers on this site.</p>
|
||||
<br />
|
||||
<br />
|
||||
<p>If you have any questions, concerns, or would like to suggest a feature, bug-fix, or request to help. Please feel</p>
|
||||
<p>free to reach out to me at <a href="mailto://derek@mistox.net">derek@mistox.net</a></p>
|
||||
|
||||
<a href='https://ko-fi.com/A0A3TSI2D' target='_blank'>
|
||||
<img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi6.png?v=6' alt='Buy Me a Coffee at ko-fi.com' />
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'legal-about',
|
||||
templateUrl: './about.component.html',
|
||||
imports: [ FormsModule, CommonModule ]
|
||||
})
|
||||
export class AboutComponent {
|
||||
|
||||
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title ) {
|
||||
this.title.setTitle("About | Mistox");
|
||||
};
|
||||
|
||||
}
|
||||
@@ -32,13 +32,13 @@ export class EditItemComponent {
|
||||
}
|
||||
|
||||
// If user is not Admin -> route home
|
||||
if (auth.loggedInUser.siteData.role != "Admin"){
|
||||
if (auth.loggedInUser.role != "Admin"){
|
||||
router.navigate(["/"]);
|
||||
}
|
||||
|
||||
// Load product
|
||||
const formData = new FormData();
|
||||
formData.append("productID", this.newItem.id.toString());
|
||||
formData.append("productID", this.newItem.id!.toString());
|
||||
this.http.post<Product>( "api/product/get", formData ).subscribe({
|
||||
next: async (data) => {
|
||||
this.newItem = data;
|
||||
|
||||
@@ -28,7 +28,7 @@ export class NewItemComponent {
|
||||
}
|
||||
|
||||
// If user is not Admin -> route home
|
||||
if (auth.loggedInUser.siteData.role != "Admin"){
|
||||
if (auth.loggedInUser.role != "Admin"){
|
||||
router.navigate(["/"]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
</div>
|
||||
<h2 class="gameCard-Price">${{ (product.cost/100).toFixed(2) }}</h2>
|
||||
<button class="gameCard-Button" >Add To Cart</button>
|
||||
<div *ngIf="auth.loggedInUser.siteData.role == 'Admin'">
|
||||
<div *ngIf="auth.loggedInUser.role == 'Admin'">
|
||||
<button style="width: calc(50% - 10px); margin: 5px;" [routerLink]="['/store/admin/edit']" [queryParams]="{ ProductID: product.id }" >
|
||||
Edit
|
||||
</button>
|
||||
<button style="width: calc(50% - 10px); margin: 5px;" (click)="DeleteItem(product.id)" >
|
||||
<button style="width: calc(50% - 10px); margin: 5px;" (click)="DeleteItem(product.id!)" >
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="auth.loggedInUser.siteData.role == 'Admin'">
|
||||
<div *ngIf="auth.loggedInUser.role == 'Admin'">
|
||||
<button style="width: calc(100% - 10px); margin: 5px;" [routerLink]="['/store/admin/new']" >
|
||||
New
|
||||
</button>
|
||||
|
||||
@@ -1,84 +1,39 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Account } from "../models/Account";
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class Authentication{
|
||||
|
||||
private _user = new BehaviorSubject<Account>(this.getUserFromStorage());
|
||||
private _user = new BehaviorSubject<Account>( new Account );
|
||||
user$ = this._user.asObservable();
|
||||
|
||||
constructor( private http: HttpClient){ }
|
||||
|
||||
Login(UserName: string, Password: string, StayLoggedIn: boolean): Observable<Account> {
|
||||
|
||||
const body = new HttpParams()
|
||||
.set("UserName", UserName)
|
||||
.set("PasswordHash", Password)
|
||||
.set("StayLoggedIn", StayLoggedIn );
|
||||
const headers = new HttpHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
});
|
||||
|
||||
let sub = this.http.post<Account>( "api/account/login", body, { headers } );
|
||||
getLoginState(): Observable<Account> {
|
||||
let sub = this.http.post<Account>( "api/account/loginState", {}, {} );
|
||||
sub.subscribe({
|
||||
next: data => {
|
||||
if (data.error.length === 0){
|
||||
this._user.next(data);
|
||||
this.setUserToStorage(data, StayLoggedIn == true ? SessionType.Forever : SessionType.Session);
|
||||
}
|
||||
},
|
||||
error: err => {
|
||||
console.log("HTTP Error Signing In: ", err);
|
||||
console.log("No login state found: ", err.error);
|
||||
}
|
||||
});
|
||||
return sub;
|
||||
}
|
||||
|
||||
Logout(){
|
||||
this.http.post<Account>( "api/account/logout", {}, { responseType: 'json' } ).subscribe( );
|
||||
this._user.next( new Account );
|
||||
this.delUserFromStorage();
|
||||
return this.http.post<Account>( "api/account/logout", {}, { responseType: 'json' } );
|
||||
}
|
||||
|
||||
get isLoggedIn(): boolean {
|
||||
return this._user.value.id != -1 ? true : false;
|
||||
return this._user.value.id != null ? true : false;
|
||||
}
|
||||
|
||||
get loggedInUser(): Account {
|
||||
return this._user.value;
|
||||
}
|
||||
|
||||
private getUserFromStorage(): Account {
|
||||
const foreverUser = localStorage.getItem('user');
|
||||
const sessionUser = sessionStorage.getItem('user');
|
||||
let user = null;
|
||||
if (foreverUser != null){
|
||||
user = JSON.parse(foreverUser)
|
||||
} else if (sessionUser != null){
|
||||
user = JSON.parse(sessionUser)
|
||||
} else {
|
||||
user = new Account();
|
||||
user.id = -1;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
private setUserToStorage(user: Account, session: SessionType): void {
|
||||
if (session == SessionType.Forever){
|
||||
localStorage.setItem('user', JSON.stringify(user));
|
||||
}else if(session == SessionType.Session){
|
||||
sessionStorage.setItem('user', JSON.stringify(user));
|
||||
}
|
||||
}
|
||||
private delUserFromStorage(): void {
|
||||
localStorage.removeItem('user');
|
||||
sessionStorage.removeItem('user');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export enum SessionType {
|
||||
Forever,
|
||||
Session
|
||||
}
|
||||
@@ -1,284 +1,48 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
using MistoxWebsite.Server.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MistoxWebsite.Server.Services.DatabaseService;
|
||||
using MistoxWebsite.Server.Entities;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
|
||||
namespace MistoxWebsite.Server.Controllers {
|
||||
[ApiController]
|
||||
[Route("api/account/[controller]")]
|
||||
[Route("api/account/")]
|
||||
public class AuthenticationController : MistoxControllerBase {
|
||||
|
||||
EmailService _emailContext;
|
||||
public AuthenticationController(DatabaseService db) : base(db) { }
|
||||
|
||||
public AuthenticationController(DatabaseService db, EmailService emailContext) : base(db) {
|
||||
_emailContext = emailContext;
|
||||
}
|
||||
|
||||
[Route("login")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Account>> Login([FromForm] string UserName, [FromForm] string PasswordHash, [FromForm] bool StayLoggedIn) {
|
||||
try {
|
||||
Account? test = await _databaseService.GetAccount(UserName.ToLower());
|
||||
if (test != null) {
|
||||
if (test.EmailVerified == true) {
|
||||
if (test.FailedPasswordLock) {
|
||||
if (test.CurrentPasswordAttempts >= test.PasswordAttempts) {
|
||||
return new Account() { Error = "Too many failed password attempts. Please reset your password" };
|
||||
}
|
||||
}
|
||||
if (BCrypt.Net.BCrypt.Verify(PasswordHash, test.PasswordHash)) {
|
||||
test.CurrentPasswordAttempts = 0;
|
||||
await _databaseService.SetAccount(test);
|
||||
|
||||
List<Claim> claims = new List<Claim>() {
|
||||
new Claim("ID", test.ID.ToString())
|
||||
};
|
||||
|
||||
await HttpContext.SignInAsync(
|
||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
new ClaimsPrincipal(new ClaimsIdentity(claims, "Auth")),
|
||||
new AuthenticationProperties {
|
||||
ExpiresUtc = DateTime.UtcNow.AddYears(30), // Add 30 years with sliding on
|
||||
IsPersistent = StayLoggedIn, // Is set from the StayLoggedIn
|
||||
}
|
||||
);
|
||||
return test;
|
||||
}
|
||||
else {
|
||||
test.CurrentPasswordAttempts += 1;
|
||||
await _databaseService.SetAccount(test);
|
||||
return new Account() { Error = "Wrong password" };
|
||||
}
|
||||
}
|
||||
else {
|
||||
await SendVerify(test.UserName);
|
||||
return new Account() { Error = "A new verify email has been sent. \n Note only 1 email send every 5 mintes" };
|
||||
}
|
||||
}
|
||||
return new Account() { Error = "User doesn't exist" };
|
||||
} catch (Exception ex) {
|
||||
return new Account() { Error = ex.Message };
|
||||
}
|
||||
}
|
||||
|
||||
[Route("register")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Account>> Register([FromForm] string Email, [FromForm] string UserName, [FromForm] string PasswordHash) {
|
||||
try {
|
||||
if (await _databaseService.GetAccount(UserName.ToLower()) == null) {
|
||||
if (await _databaseService.GetAccount(Email.ToLower()) == null) {
|
||||
Account? created = new Account() {
|
||||
UserName = UserName.ToLower(),
|
||||
Email = Email.ToLower(),
|
||||
EmailVerified = false,
|
||||
PasswordHash = BCrypt.Net.BCrypt.HashPassword(PasswordHash),
|
||||
};
|
||||
await _databaseService.SetAccount(created);
|
||||
created = await _databaseService.GetAccount(Email.ToLower());
|
||||
if (created != null) {
|
||||
await SendVerify(created.UserName);
|
||||
return created;
|
||||
}
|
||||
return new Account() { Error = "Unknown Error" };
|
||||
}
|
||||
else {
|
||||
return new Account() { Error = "Email is already in use" };
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new Account() { Error = "UserName is taken" };
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine("Error: " + ex.Message);
|
||||
return new Account() { Error = ex.Message };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Route("changepassword")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<bool>> ChangePassword([FromForm] string OldPassword, [FromForm] string NewPassword) {
|
||||
try {
|
||||
[HttpPost("loginstate")]
|
||||
public ActionResult<Account> LoginState() {
|
||||
if (isLoggedIn()) {
|
||||
Account user = await getLoggedInUser();
|
||||
if (BCrypt.Net.BCrypt.Verify(OldPassword, user.PasswordHash)) {
|
||||
user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(NewPassword);
|
||||
user.CurrentPasswordAttempts = 0;
|
||||
await _databaseService.SetAccount(user);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
return Ok(getLoggedInUser());
|
||||
}
|
||||
return NotFound("Not logged in");
|
||||
}
|
||||
|
||||
[Route("toggleaccountlock")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<string>> ToggleAccountLock([FromForm] bool AccountLock) {
|
||||
try {
|
||||
if (isLoggedIn()) {
|
||||
Account user = await getLoggedInUser();
|
||||
user.FailedPasswordLock = AccountLock;
|
||||
user.CurrentPasswordAttempts = 0;
|
||||
await _databaseService.SetAccount(user);
|
||||
return "Account Lock Status Updated";
|
||||
}
|
||||
return "Unknown Error Occurred";
|
||||
} catch (Exception ex) {
|
||||
return ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
[Route("get")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Account?>> Get() {
|
||||
try {
|
||||
if (isLoggedIn()) {
|
||||
return await getLoggedInUser();
|
||||
}
|
||||
return Ok();
|
||||
} catch {
|
||||
[HttpPost("loginticket")]
|
||||
public async Task<ActionResult> LoginTicket([FromBody] string LoginToken) {
|
||||
using (HttpClient client = new HttpClient()) {
|
||||
var payload = new { ticket = LoginToken };
|
||||
StringContent jsonPayload = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
|
||||
HttpResponseMessage JWTResponse = await client.PostAsync("https://auth.mistox.com/api/auth/token", jsonPayload);
|
||||
if (JWTResponse.IsSuccessStatusCode) {
|
||||
string JWT = await JWTResponse.Content.ReadAsStringAsync();
|
||||
signIn(JWT);
|
||||
return Ok();
|
||||
} else {
|
||||
string error = await JWTResponse.Content.ReadAsStringAsync();
|
||||
return NotFound(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Route("logout")]
|
||||
[HttpPost]
|
||||
public async Task Logout() {
|
||||
await HttpContext.SignOutAsync();
|
||||
}
|
||||
|
||||
[Route("sendverifyemail")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<string>> SendVerify([FromForm] string UserName) {
|
||||
try {
|
||||
string key = "v" + UserName;
|
||||
// Stop from sending multiple emails quickly
|
||||
if (_emailContext._SentEmails.ContainsKey(key)) {
|
||||
DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key);
|
||||
if (PreviousSentTime.AddMinutes(5) > DateTime.Now) {
|
||||
return "Cannot sent another verify email until 5 minutes has elapsed ";
|
||||
}
|
||||
else {
|
||||
_emailContext._SentEmails.Remove(key);
|
||||
}
|
||||
}
|
||||
Account? test = await _databaseService.GetAccount(UserName.ToLower());
|
||||
if (test != null) {
|
||||
test.EmailToken = Guid.NewGuid().ToString();
|
||||
await _databaseService.SetAccount(test);
|
||||
|
||||
string EmailContents = EmailService.VerifyEmailEmail;
|
||||
EmailContents = Substitue(EmailContents, "@UserName", UserName);
|
||||
EmailContents = Substitue(EmailContents, "@UserName", UserName);
|
||||
EmailContents = Substitue(EmailContents, "@VerifyPassword", test.EmailToken);
|
||||
|
||||
string result = _emailContext.Send(test.Email, EmailService.VerifyEmailSubject, EmailContents);
|
||||
_emailContext._SentEmails.Add(key, DateTime.Now);
|
||||
return result;
|
||||
}
|
||||
return "Account not found";
|
||||
} catch (Exception) {
|
||||
return "The connection couldn't be established to the email server";
|
||||
}
|
||||
}
|
||||
|
||||
[Route("verifyemail")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<bool>> VerifyEmail([FromForm] string UserName, [FromForm] string EmailToken) {
|
||||
try {
|
||||
Account? test = await _databaseService.GetAccount(UserName.ToLower());
|
||||
if (test != null) {
|
||||
if (!string.IsNullOrEmpty(test.EmailToken) && test.EmailToken == EmailToken) {
|
||||
test.EmailToken = "";
|
||||
test.EmailVerified = true;
|
||||
await _databaseService.SetAccount(test);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Route("sendresetpassword")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<string>> ResetPassword([FromForm] string Email) {
|
||||
try {
|
||||
string key = "p" + Email.ToLower();
|
||||
// Stop from sending multiple emails quickly
|
||||
if (_emailContext._SentEmails.ContainsKey(key)) {
|
||||
DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key);
|
||||
if (PreviousSentTime.AddMinutes(5) > DateTime.Now) {
|
||||
return "Cannot sent another reset requests until 5 minutes has elapsed";
|
||||
}
|
||||
else {
|
||||
_emailContext._SentEmails.Remove(key);
|
||||
}
|
||||
}
|
||||
Account? test = await _databaseService.GetAccount(Email.ToLower());
|
||||
if (test != null) {
|
||||
test.EmailToken = Guid.NewGuid().ToString();
|
||||
await _databaseService.SetAccount(test);
|
||||
|
||||
string EmailContents = EmailService.ResetPasswordEmail;
|
||||
EmailContents = Substitue(EmailContents, "@UserName", test.UserName);
|
||||
EmailContents = Substitue(EmailContents, "@UserName", test.UserName);
|
||||
EmailContents = Substitue(EmailContents, "@ResetPassWord", test.EmailToken);
|
||||
|
||||
string result = _emailContext.Send(test.Email, EmailService.VerifyEmailSubject, EmailContents);
|
||||
_emailContext._SentEmails.Add(key, DateTime.Now);
|
||||
return result;
|
||||
}
|
||||
return "Account Not Found";
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine("EmailService Error: " + e.ToString());
|
||||
return "The connection couldn't be established to the email server";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Route("resetpassword")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<bool>> ResetPwdVerify([FromForm] string UserName, [FromForm] string NewPassword, [FromForm] string ResetToken) {
|
||||
try {
|
||||
Account? test = await _databaseService.GetAccount(UserName.ToLower());
|
||||
if (test != null && !string.IsNullOrEmpty(test.EmailToken)) {
|
||||
if (!string.IsNullOrEmpty(test.EmailToken) && test.EmailToken == ResetToken) {
|
||||
test.CurrentPasswordAttempts = 0;
|
||||
test.EmailToken = "";
|
||||
test.PasswordHash = BCrypt.Net.BCrypt.HashPassword(NewPassword);
|
||||
await _databaseService.SetAccount(test);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Route("delete")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<bool>> delete([FromForm] string Password) {
|
||||
try {
|
||||
[HttpGet("logout")]
|
||||
public ActionResult Logout() {
|
||||
if (isLoggedIn()) {
|
||||
Account user = await getLoggedInUser();
|
||||
if (BCrypt.Net.BCrypt.Verify(Password, user.PasswordHash)) {
|
||||
await _databaseService.DeleteAccount(user.ID);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
signOut();
|
||||
return Redirect("/");
|
||||
}
|
||||
return NotFound("Not logged in");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
using MistoxWebsite.Server.Entities;
|
||||
using MistoxWebsite.Server.Services.DatabaseService;
|
||||
|
||||
@@ -12,6 +13,19 @@ namespace MistoxWebsite.Server.Controllers {
|
||||
_databaseService = databaseService;
|
||||
}
|
||||
|
||||
public void signIn(string JWT) {
|
||||
Response.Cookies.Append("mistox_session", JWT, new CookieOptions {
|
||||
Secure = true,
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Strict,
|
||||
Expires = DateTime.UtcNow.AddDays(7)
|
||||
});
|
||||
}
|
||||
|
||||
public void signOut() {
|
||||
Response.Cookies.Delete("mistox_session");
|
||||
}
|
||||
|
||||
public bool isLoggedIn() {
|
||||
if (User.Identity != null && User.Identity.IsAuthenticated) {
|
||||
return true;
|
||||
@@ -20,16 +34,19 @@ namespace MistoxWebsite.Server.Controllers {
|
||||
}
|
||||
|
||||
public int getLoggedInUserID() {
|
||||
return Convert.ToInt32(User.FindFirst("ID")?.Value);
|
||||
return Convert.ToInt32(User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||
}
|
||||
|
||||
public async Task<Account> getLoggedInUser() {
|
||||
public Account getLoggedInUser() {
|
||||
try {
|
||||
Account? test = await _databaseService.GetAccount(getLoggedInUserID());
|
||||
if (test != null) {
|
||||
return test;
|
||||
}
|
||||
return new Account();
|
||||
Account building = new Account {
|
||||
ID = Convert.ToInt32(User.FindFirstValue(ClaimTypes.NameIdentifier)),
|
||||
UserName = User.FindFirstValue(ClaimTypes.Name)!.ToString(),
|
||||
Email = User.FindFirstValue(ClaimTypes.Email)!.ToString(),
|
||||
Role = User.FindFirstValue(ClaimTypes.Role)!.ToString(),
|
||||
DataServer = User.FindFirstValue(ClaimTypes.UserData)!.ToString()
|
||||
};
|
||||
return building;
|
||||
} catch {
|
||||
return new Account();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace MistoxWebsite.Server.Controllers {
|
||||
public async Task<ActionResult<bool>> CreateProduct([FromForm] Product obj, [FromForm] IFormFile[] images) {
|
||||
try {
|
||||
if (isLoggedIn()) {
|
||||
Account user = await getLoggedInUser();
|
||||
Account user = getLoggedInUser();
|
||||
if (user.Role == "Admin") {
|
||||
List<ProductImage> building = new List<ProductImage>();
|
||||
foreach (var file in images) {
|
||||
@@ -70,7 +70,7 @@ namespace MistoxWebsite.Server.Controllers {
|
||||
public async Task<ActionResult<bool>> DeleteProduct([FromForm] int productID) {
|
||||
try {
|
||||
if (isLoggedIn()) {
|
||||
Account user = await getLoggedInUser();
|
||||
Account user = getLoggedInUser();
|
||||
if (user.Role == "Admin") {
|
||||
await _databaseService.DeleteProduct(productID);
|
||||
return true;
|
||||
|
||||
@@ -3,17 +3,11 @@
|
||||
namespace MistoxWebsite.Server.Entities {
|
||||
|
||||
public class Account {
|
||||
public int ID { get; set; } // PK
|
||||
public int? ID { get; set; } // PK
|
||||
public string UserName { get; set; } = "";
|
||||
public string Email { get; set; } = "";
|
||||
public bool EmailVerified { get; set; } = false;
|
||||
public string PasswordHash { get; set; } = "";
|
||||
public bool FailedPasswordLock { get; set; } = false;
|
||||
public int PasswordAttempts { get; set; } = 5;
|
||||
public int CurrentPasswordAttempts { get; set; } = 0;
|
||||
public string Role { get; set; } = "Generic";
|
||||
public string EmailToken { get; set; } = "";
|
||||
public string Error { get; set; } = "";
|
||||
public string DataServer { get; set; } = "";
|
||||
}
|
||||
|
||||
public class Product {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.WebApiCompatShim" Version="2.3.0" />
|
||||
<PackageReference Include="MySql.Data" Version="9.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.RateLimiting;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using MistoxWebsite.Server.Controllers.Payment;
|
||||
using MistoxWebsite.Server.Services;
|
||||
using MistoxWebsite.Server.Services.DatabaseService;
|
||||
@@ -78,25 +84,97 @@ if (IPayment._PaymentType == PaymentType.StripeIntent) {
|
||||
IPayment._EndpointSecret = string.IsNullOrEmpty(StripeEndpointKey) ? "" : StripeEndpointKey;
|
||||
}
|
||||
|
||||
// Authentication Service
|
||||
builder.Services.AddAuthentication( options => {
|
||||
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
} ).AddCookie(options => {
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||
options.Cookie.SameSite = SameSiteMode.Strict;
|
||||
options.LoginPath = "/account/login";
|
||||
options.LogoutPath = "/account/logout";
|
||||
options.SlidingExpiration = true;
|
||||
////////////////////////////////
|
||||
/////// Auth Service ////////
|
||||
////////////////////////////////
|
||||
|
||||
RsaSecurityKey? PublicKey = null;
|
||||
using (HttpClient client = new HttpClient()) {
|
||||
while (PublicKey == null) {
|
||||
HttpResponseMessage PublicKeyResponse = await client.GetAsync("https://auth.mistox.com/api/auth/publickey");
|
||||
if (PublicKeyResponse.IsSuccessStatusCode) {
|
||||
string publicKey = await PublicKeyResponse.Content.ReadAsStringAsync();
|
||||
RSA rsa = RSA.Create();
|
||||
rsa.ImportFromPem(publicKey);
|
||||
PublicKey = new RsaSecurityKey(rsa);
|
||||
} else {
|
||||
await Task.Delay(2000); // sleep the main thread for 2 seconds before sending another request. Prevent DDOS of my own equiptment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.Services.AddAuthentication(options => {
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
}).AddJwtBearer(options => {
|
||||
options.TokenValidationParameters = new TokenValidationParameters {
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = "https://auth.mistox.com",
|
||||
ValidAudience = "mistox-llc-auth-token",
|
||||
IssuerSigningKey = PublicKey,
|
||||
ClockSkew = TimeSpan.FromMinutes(1)
|
||||
};
|
||||
options.Events = new JwtBearerEvents {
|
||||
OnMessageReceived = context => {
|
||||
context.Token = context.Request.Cookies["mistox_session"];
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnTokenValidated = context => {
|
||||
var jwtToken = context.SecurityToken as JwtSecurityToken;
|
||||
if (jwtToken != null) {
|
||||
var exp = jwtToken.ValidTo;
|
||||
var now = DateTime.UtcNow;
|
||||
if ((exp - now) < TimeSpan.FromDays(3)) {
|
||||
// Impliment token refresh
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddCors( o => o.AddDefaultPolicy( builder => {
|
||||
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); // No CORS
|
||||
} ) );
|
||||
////////////////////////////////
|
||||
/// Rate Limiting Service ////
|
||||
////////////////////////////////
|
||||
|
||||
List<string> allowedOrigins = new List<string>{ "https://mistox.com", "https://www.mistox.com" };
|
||||
if (builder.Environment.IsDevelopment()) {
|
||||
allowedOrigins.Add("http://localhost:5000");
|
||||
}
|
||||
|
||||
builder.Services.AddCors(options => {
|
||||
options.AddDefaultPolicy(policy => {
|
||||
policy.WithOrigins(allowedOrigins.ToArray())
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddRateLimiter(options => {
|
||||
options.AddPolicy("PerUserPolicy", httpContext => {
|
||||
var userId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
||||
?? $"ip:{httpContext.Connection.RemoteIpAddress}";
|
||||
|
||||
return RateLimitPartition.GetTokenBucketLimiter(userId, key => new TokenBucketRateLimiterOptions {
|
||||
TokenLimit = 10, // max 10 requests
|
||||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
||||
QueueLimit = 0,
|
||||
ReplenishmentPeriod = TimeSpan.FromSeconds(15),
|
||||
TokensPerPeriod = 2,
|
||||
AutoReplenishment = true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
////////////////////////////////
|
||||
///// ASPNET Core Function /////
|
||||
////////////////////////////////
|
||||
|
||||
// Pages Service
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddRazorPages();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -108,6 +186,8 @@ if( !app.Environment.IsDevelopment() ) {
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRateLimiter();
|
||||
|
||||
app.UseCors();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
using MistoxWebsite.Server.Entities;
|
||||
using MySql.Data.MySqlClient;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace MistoxWebsite.Server.Services.DatabaseService {
|
||||
public partial class DatabaseService {
|
||||
|
||||
public async Task<Account?> GetAccount( string UserNameOrEmail ) {
|
||||
Account? account = null;
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
connection.Open();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM Account
|
||||
WHERE UserName = @UorE OR Email = @UorE;
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@UorE", UserNameOrEmail);
|
||||
|
||||
using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) {
|
||||
while( await reader.ReadAsync() ) {
|
||||
if( reader == null ) {
|
||||
break;
|
||||
}
|
||||
|
||||
int _id = reader.GetInt32("ID");
|
||||
string _username = reader.GetString("UserName");
|
||||
string _email = reader.GetString("Email");
|
||||
bool _emailVerified = reader.GetBoolean("EmailVerified");
|
||||
string _passwordhash = reader.GetString("PasswordHash");
|
||||
bool _failedpasswordlock = reader.GetBoolean( "FailedPasswordLock" );
|
||||
int _passwordattempts = reader.GetInt32( "PasswordAttempts" );
|
||||
int _curpasswordattempts = reader.GetInt32( "CurrentPasswordAttempts" );
|
||||
string _role = reader.GetString( "Role" );
|
||||
string _emailtoken = reader.GetString( "EmailToken" );
|
||||
|
||||
account = new Account() {
|
||||
ID = _id,
|
||||
UserName = _username,
|
||||
Email = _email,
|
||||
EmailVerified = _emailVerified,
|
||||
PasswordHash = _passwordhash,
|
||||
CurrentPasswordAttempts = _curpasswordattempts,
|
||||
PasswordAttempts = _passwordattempts,
|
||||
EmailToken = _emailtoken,
|
||||
FailedPasswordLock = _failedpasswordlock,
|
||||
Role = _role,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
public async Task<Account?> GetAccount( int ID ) {
|
||||
Account? account = null;
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
connection.Open();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM Account
|
||||
WHERE ID = @ID;
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@ID", ID);
|
||||
|
||||
using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) {
|
||||
while( await reader.ReadAsync() ) {
|
||||
if( reader == null ) {
|
||||
break;
|
||||
}
|
||||
int _id = reader.GetInt32("ID");
|
||||
string _username = reader.GetString("UserName");
|
||||
string _email = reader.GetString("Email");
|
||||
bool _emailVerified = reader.GetBoolean("EmailVerified");
|
||||
string _passwordhash = reader.GetString("PasswordHash");
|
||||
bool _failedpasswordlock = reader.GetBoolean( "FailedPasswordLock" );
|
||||
int _passwordattempts = reader.GetInt32( "PasswordAttempts" );
|
||||
int _curpasswordattempts = reader.GetInt32( "CurrentPasswordAttempts" );
|
||||
string _role = reader.GetString( "Role" );
|
||||
string _emailtoken = reader.GetString( "EmailToken" );
|
||||
|
||||
account = new Account() {
|
||||
ID = _id,
|
||||
UserName = _username,
|
||||
Email = _email,
|
||||
EmailVerified = _emailVerified,
|
||||
PasswordHash = _passwordhash,
|
||||
CurrentPasswordAttempts = _passwordattempts,
|
||||
PasswordAttempts = _passwordattempts,
|
||||
EmailToken = _emailtoken,
|
||||
FailedPasswordLock = _failedpasswordlock,
|
||||
Role = _role,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
public async Task SetAccount( Account Profile ) {
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
connection.Open();
|
||||
|
||||
string command = @"
|
||||
INSERT INTO Account
|
||||
(ID,UserName,Email,EmailVerified,PasswordHash,FailedPasswordLock,PasswordAttempts,CurrentPasswordAttempts,Role,EmailToken)
|
||||
VALUES
|
||||
(@ID,@UserName,@Email,@EmailVerified,@PasswordHash,@FailedPasswordLock,@PasswordAttempts,@CurrentPasswordAttempts,@Role,@EmailToken);
|
||||
ON DUPLICATE KEY UPDATE
|
||||
UserName = @UserName,
|
||||
Email = @Email,
|
||||
EmailVerified = @EmailVerified,
|
||||
PasswordHash = @PasswordHash,
|
||||
FailedPasswordLock = @FailedPasswordLock,
|
||||
PasswordAttempts = @PasswordAttempts,
|
||||
CurrentPasswordAttempts = @CurrentPasswordAttempts,
|
||||
Role = @Role,
|
||||
EmailToken = @EmailToken;
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand( command , connection);
|
||||
cmd.Parameters.AddWithValue("@ID", Profile.ID);
|
||||
cmd.Parameters.AddWithValue("@UserName", Profile.UserName);
|
||||
cmd.Parameters.AddWithValue("@Email", Profile.Email);
|
||||
cmd.Parameters.AddWithValue("@EmailVerified", Profile.EmailVerified);
|
||||
cmd.Parameters.AddWithValue("@PasswordHash", Profile.PasswordHash);
|
||||
cmd.Parameters.AddWithValue("@FailedPasswordLock", Profile.FailedPasswordLock);
|
||||
cmd.Parameters.AddWithValue("@PasswordAttempts", Profile.PasswordAttempts);
|
||||
cmd.Parameters.AddWithValue("@CurrentPasswordAttempts", Profile.CurrentPasswordAttempts);
|
||||
cmd.Parameters.AddWithValue("@Role", Profile.Role);
|
||||
cmd.Parameters.AddWithValue("@EmailToken", Profile.EmailToken);
|
||||
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAccount( int ID ) {
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
MySqlCommand cmd;
|
||||
connection.Open();
|
||||
|
||||
string command = @"
|
||||
DELETE FROM Account WHERE ID = @ID;
|
||||
DELETE FROM AccountInventory WHERE AccountID = @ID;
|
||||
DELETE FROM ProjectMistData WHERE AccountID = @ID;
|
||||
DELETE FROM Cart WHERE AccountID = @ID;
|
||||
";
|
||||
cmd = new MySqlCommand( command, connection );
|
||||
cmd.Parameters.AddWithValue("@ID", ID);
|
||||
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user