75 Commits

Author SHA1 Message Date
derek 19158f6068 fix build script
Docker Build and Release Upload / build (push) Successful in 30s
2025-07-09 19:32:05 -07:00
derek 4f7a8619c7 Add CD/CI file 2025-07-09 19:22:24 -07:00
derek 052a19ab28 finalize ci/cd build
Docker Build and Release Upload / build (push) Successful in 25s
2025-07-10 02:06:54 +00:00
derek 3d45a228d9 update echo cmd
Docker Build and Release Upload / build (push) Successful in 23s
2025-07-10 02:01:03 +00:00
derek 29cb968e1a write releaseid to file for persistance
Docker Build and Release Upload / build (push) Failing after 21s
2025-07-10 01:58:44 +00:00
derek a89e1d9a46 put all important files on root
Docker Build and Release Upload / build (push) Successful in 20s
2025-07-10 01:54:30 +00:00
derek f80d8c1003 change all paths
Docker Build and Release Upload / build (push) Failing after 3s
2025-07-10 01:53:46 +00:00
derek 223dbc9b50 change path
Docker Build and Release Upload / build (push) Successful in 25s
2025-07-10 01:52:25 +00:00
derek f3293d8db2 remove apt
Docker Build and Release Upload / build (push) Successful in 21s
2025-07-10 01:50:45 +00:00
derek 819133e5c3 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 17s
2025-07-10 01:50:06 +00:00
derek 05a8345046 try with jq but pull jq first
Docker Build and Release Upload / build (push) Failing after 19s
2025-07-10 01:35:21 +00:00
derek 62213ad061 Use sed instead of jq
Docker Build and Release Upload / build (push) Successful in 22s
2025-07-10 01:32:44 +00:00
derek 44b738093d test for shell variables
Docker Build and Release Upload / build (push) Failing after 19s
2025-07-10 01:31:33 +00:00
derek 5fe2e7a5c1 fix bad space
Docker Build and Release Upload / build (push) Successful in 21s
2025-07-10 01:27:59 +00:00
derek a3a37c4130 Create release first
Docker Build and Release Upload / build (push) Failing after 22s
2025-07-10 01:27:13 +00:00
derek d2af49b62f update url
Docker Build and Release Upload / build (push) Successful in 19s
2025-07-10 01:08:26 +00:00
derek 0e75b87908 try web api
Docker Build and Release Upload / build (push) Failing after 17s
2025-07-10 01:07:07 +00:00
derek 3ce94b8e0a Try cmd publish
Docker Build and Release Upload / build (push) Failing after 1m33s
2025-07-10 01:01:29 +00:00
derek fae233001b Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 20s
2025-07-03 23:18:51 +00:00
derek 8e7d78f89c Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 2s
2025-07-03 23:18:30 +00:00
derek d31ddf6995 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 1s
2025-07-03 23:17:40 +00:00
derek 7f8a7f900b Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 23:17:14 +00:00
derek a7d58fbb45 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 23:16:50 +00:00
derek 63d1eff0f0 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 23:15:41 +00:00
derek c53fac38d4 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 23:14:11 +00:00
derek e05dd2769c Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 16s
2025-07-03 23:10:49 +00:00
derek 81f19cb609 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 16s
2025-07-03 23:07:50 +00:00
derek 8bf9cdb421 Update .gitea/workflows/demo.yaml 2025-07-03 23:06:12 +00:00
derek 46543cf38c Update .gitea/workflows/demo.yaml 2025-07-03 23:05:07 +00:00
derek 8288721ab7 Update .gitea/workflows/demo.yaml 2025-07-03 23:03:11 +00:00
derek 9a00fd09b1 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Successful in 2m3s
2025-07-03 22:54:34 +00:00
derek e913b52c4f Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 31s
2025-07-03 22:53:19 +00:00
derek bce5bbb405 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Successful in 3s
2025-07-03 22:37:31 +00:00
derek fbf73ce1b2 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Successful in 4s
2025-07-03 22:36:21 +00:00
derek 1ca80a6404 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Successful in 3s
2025-07-03 22:33:04 +00:00
derek 73a60698b2 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 3s
2025-07-03 22:31:48 +00:00
derek 5fca8258aa Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Successful in 1m3s
2025-07-03 22:20:04 +00:00
derek 7d86bfe33d Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Successful in 2s
2025-07-03 22:13:19 +00:00
derek b420947f7f Update .gitea/workflows/demo.yaml 2025-07-03 22:12:38 +00:00
derek 8d1ef3ba76 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 22:10:21 +00:00
derek 5687aefe70 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 22:08:14 +00:00
derek 8f4b0079b1 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 22:05:03 +00:00
derek 715ed383e2 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Has been cancelled
2025-07-03 22:03:40 +00:00
derek 0a3ebe539f Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 1s
2025-07-03 21:59:27 +00:00
derek e1ee169904 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 10s
2025-07-03 21:57:09 +00:00
derek 0fdd16a906 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 1s
2025-07-03 01:18:24 +00:00
derek cf5ff89bcd Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 1s
2025-07-03 01:09:15 +00:00
derek 44ea945ebf Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 1s
2025-07-03 01:06:39 +00:00
derek cc4d491d60 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 2s
2025-07-03 01:04:11 +00:00
derek 0692f6b185 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 13s
2025-07-03 01:00:15 +00:00
derek 8a10fe140b Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 14s
2025-07-03 00:58:00 +00:00
derek baaa8398e6 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 20s
2025-07-03 00:55:35 +00:00
derek 5290a5ddd9 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 2s
2025-07-03 00:54:22 +00:00
derek 88c89a5efd Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 2s
2025-07-03 00:53:22 +00:00
derek 7c627b9167 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 39s
2025-07-03 00:51:29 +00:00
derek 06e21b87b9 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 1s
2025-07-03 00:49:32 +00:00
derek 61b3611a01 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 00:48:41 +00:00
derek c2dce2010d Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 00:48:25 +00:00
derek a5b23e4e31 Update .gitea/workflows/demo.yaml
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 00:47:28 +00:00
derek b70cdf4725 change to gitea checkout
Docker Build and Release Upload / build (push) Failing after 0s
2025-07-03 00:45:19 +00:00
derek a4a2d654ee update build
Docker Build and Release Upload / build (push) Failing after 10s
2025-07-03 00:43:34 +00:00
derek 6ae0a3d3b4 Simple test 2025-07-03 00:43:04 +00:00
derek e6e4ad3c0b Update secret token 2025-07-03 00:39:51 +00:00
derek 709ddc4b2f chatgpt test 2025-07-03 00:34:04 +00:00
derek b553472805 new testt
Build-Release / Build-Release (push) Failing after 3s
2025-07-03 00:28:30 +00:00
derek 3ee858461f new test
Build-Release / Build-Release (push) Failing after 16s
2025-07-03 00:25:12 +00:00
derek 7d099a19a5 new test
Build-Release / Build-Release (push) Failing after 11s
2025-07-03 00:23:16 +00:00
derek 516f92e4b2 new test
Build-Release / Build-Release (push) Failing after 29s
2025-07-03 00:15:45 +00:00
derek af2c97a2af Update CD/CI
Build-Release / Build-Release (push) Failing after 17s
Test functionality
2025-07-03 00:12:27 +00:00
derek e7602ab13b reset tests back to default
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
2025-07-01 22:43:51 -07:00
derek d4adc58b9d Update .gitea/workflows/demo.yaml
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 2s
2025-07-02 05:32:23 +00:00
derek 9a877a049a update runner
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2025-07-01 21:40:22 -07:00
derek 99ab52e9cc fix issue
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 2s
2025-07-01 21:31:38 -07:00
derek ca20770a72 Update action URL
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2025-07-01 21:28:15 -07:00
derek 460400bd7e Push test workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 26s
2025-07-01 18:31:36 -07:00
39 changed files with 1223 additions and 269 deletions
-1
View File
@@ -14,4 +14,3 @@ csharp_new_line_before_open_brace = none
csharp_new_line_before_catch = false csharp_new_line_before_catch = false
csharp_new_line_before_finally = false csharp_new_line_before_finally = false
csharp_new_line_after_else = false csharp_new_line_after_else = false
csharp_new_line_before_else = false
+45 -17
View File
@@ -7,27 +7,55 @@ on:
jobs: jobs:
build: build:
runs-on: alpine-linux runs-on: ubuntu-latest
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Docker Buildx - name: build database
uses: docker/setup-buildx-action@v3
- name: build and push database
run: | run: |
docker buildx build \ docker build -t mistox-sql ./database
--platform=linux/amd64,linux/arm64 \
-t docker.mistox.net/mistox-sql \
--push \
./database
- name: build and push server - name: build server
run: | run: |
docker buildx build \ docker build --build-arg BASE_URL=https://mistox.com -t mistox-website .
--platform=linux/amd64,linux/arm64 \
--build-arg BASE_URL=https://mistox.com \ - name: create release folder
-t docker.mistox.net/mistox-website \ run: |
--push \ mkdir release
.
- name: export database
run: docker save mistox-sql -o /mistox-sql.tar
- name: export server
run: docker save mistox-website -o /mistox-website.tar
- name: create release
run: |
RESPONSE=$(curl -X POST -H "Authorization: token ${{ secrets.PUBLISH_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"tag_name": "release",
"name": "Release live",
"body": "This is an automated release",
"draft": false,
"prerelease": false
}' \
https://git.mistox.net/api/v1/repos/derek/MistoxCom-Angular/releases) && \
echo "$RESPONSE" | grep -o '"id":[ ]*[0-9]*' | head -n 1 | grep -o '[0-9]*' > /release_id.txt
- name: publish database
run: |
RELEASE_ID=$(cat /release_id.txt)
curl -X POST -H "Authorization: token ${{ secrets.PUBLISH_TOKEN }}" \
-F name="mistox-sql.tar" \
-F attachment=@/mistox-sql.tar \
https://git.mistox.net/api/v1/repos/derek/MistoxCom-Angular/releases/$RELEASE_ID/assets
- name: publish server
run: |
RELEASE_ID=$(cat /release_id.txt)
curl -X POST -H "Authorization: token ${{ secrets.PUBLISH_TOKEN }}" \
-F name="mistox-website.tar" \
-F attachment=@/mistox-website.tar \
https://git.mistox.net/api/v1/repos/derek/MistoxCom-Angular/releases/$RELEASE_ID/assets
-1
View File
@@ -22,7 +22,6 @@
}, },
"args": [ "args": [
"build", "build",
"--configuration=development",
"--base-href=http://localhost:5000" "--base-href=http://localhost:5000"
], ],
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
+3 -11
View File
@@ -2,7 +2,7 @@
## Build Frontend ## ## Build Frontend ##
###################### ######################
FROM --platform=$BUILDPLATFORM node:alpine AS build-frontend FROM node:alpine AS build-frontend
WORKDIR /src WORKDIR /src
# Define base address # Define base address
@@ -27,7 +27,7 @@ RUN ng build --base-href=${BASE_URL}
## Build Backend ## ## Build Backend ##
##################### #####################
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build-backend FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-backend
WORKDIR /src WORKDIR /src
# Copy the csproj # Copy the csproj
@@ -39,16 +39,8 @@ RUN dotnet restore './MistoxWebsite.Server.csproj'
# Copy the rest of the backend over # Copy the rest of the backend over
COPY ./src/MistoxWebsite.Server/ ./ COPY ./src/MistoxWebsite.Server/ ./
# Get the target arch
ARG TARGETARCH
# Build the source # Build the source
RUN set -e && \ RUN dotnet publish './MistoxWebsite.Server.csproj' -c Release -o /app/publish
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 ## ## Publish ##
+39
View File
@@ -1,6 +1,20 @@
CREATE DATABASE IF NOT EXISTS `mistox`; CREATE DATABASE IF NOT EXISTS `mistox`;
USE `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` ( CREATE TABLE IF NOT EXISTS `Product` (
`ID` int(11) NOT NULL AUTO_INCREMENT, `ID` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(45) DEFAULT NULL, `Name` varchar(45) DEFAULT NULL,
@@ -47,3 +61,28 @@ CREATE TABLE IF NOT EXISTS `Receipt` (
`TotalCost` int(11) DEFAULT NULL, `TotalCost` int(11) DEFAULT NULL,
PRIMARY KEY (`AccountID`,`ProductID`,`ReceiptID`) 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
View File
@@ -2,7 +2,7 @@ services:
mistox-server: mistox-server:
container_name: mistox_server container_name: mistox_server
image: docker.mistox.net/mistox-website:latest image: mistox-website:latest
restart: always restart: always
environment: environment:
- PaymentService=${Payment_Service} - PaymentService=${Payment_Service}
@@ -24,7 +24,7 @@ services:
mistox-database: mistox-database:
container_name: mistox_database container_name: mistox_database
image: docker.mistox.net/mistox-sql:latest image: mistox-sql:latest
restart: always restart: always
volumes: volumes:
- ./data:/var/lib/mysql - ./data:/var/lib/mysql
+6 -11
View File
@@ -41,18 +41,13 @@
</div> </div>
<!-- Login Stuff --> <!-- Login Stuff -->
<div class="nav-login"> <div class="nav-login">
<div *ngIf="auth.isLoggedIn" class="top-bar-buttons flex-right"> <div *ngIf="auth.isLoggedIn">
<a class="nav-login-button" href="https://auth.mistox.com/"><span>{{ auth.loggedInUser.userName.toUpperCase() }}</span></a> <a class="nav-login-button" href="/account/settings"><span>{{ auth.loggedInUser.userName }}</span></a>
<a class="nav-login-button" href="/api/account/logout"><span>LOGOUT</span></a> <a class="nav-login-button" href="/account/logout"><span>Logout</span></a>
</div> </div>
<div *ngIf="!auth.isLoggedIn" class="top-bar-buttons flex-right"> <div *ngIf="!auth.isLoggedIn">
<a class="nav-login-button" href="/account/login"><span>Login</span></a>
<!-- Testing Login --> <a class="nav-login-button" href="/account/register"><span>Register</span></a>
<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>
</div> </div>
</nav> </nav>
@@ -1,11 +1,28 @@
import { Routes } from '@angular/router'; 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 { MistComponent } from './pages/project/mist/mist.component';
import { CatalogComponent } from './pages/store/catalog/catalog.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 { NewItemComponent } from './pages/store/admin/newitem/new.component';
import { EditItemComponent } from './pages/store/admin/edititem/edit.component'; import { EditItemComponent } from './pages/store/admin/edititem/edit.component';
export const routes: Routes = [ 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 // Projects
{ path: "project/mist", component: MistComponent }, { path: "project/mist", component: MistComponent },
@@ -16,4 +33,6 @@ export const routes: Routes = [
{ path: "store/admin/new", component: NewItemComponent }, { path: "store/admin/new", component: NewItemComponent },
{ path: "store/admin/edit", component: EditItemComponent }, { path: "store/admin/edit", component: EditItemComponent },
// Legal
{ path: "about", component: AboutComponent },
] ]
+4 -31
View File
@@ -1,8 +1,7 @@
import { Component, ElementRef, isDevMode, ViewChild } from '@angular/core'; import { Component, ElementRef, ViewChild } from '@angular/core';
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { Router, RouterOutlet } from '@angular/router';
import { Authentication } from './services/Authentication'; import { Authentication } from './services/Authentication';
import { CommonModule, Location } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -12,38 +11,12 @@ import { HttpClient } from '@angular/common/http';
}) })
export class App { export class App {
devMode: boolean = false;
@ViewChild('homeLink') homeLink!: ElementRef<HTMLAnchorElement>; @ViewChild('homeLink') homeLink!: ElementRef<HTMLAnchorElement>;
@ViewChild('mistLink') mistLink!: ElementRef<HTMLAnchorElement>; @ViewChild('mistLink') mistLink!: ElementRef<HTMLAnchorElement>;
@ViewChild('storeLink') storeLink!: ElementRef<HTMLAnchorElement>; @ViewChild('storeLink') storeLink!: ElementRef<HTMLAnchorElement>;
@ViewChild('aboutLink') aboutLink!: ElementRef<HTMLAnchorElement>; @ViewChild('aboutLink') aboutLink!: ElementRef<HTMLAnchorElement>;
constructor( private http: HttpClient, public auth: Authentication, private router: Router, private route: ActivatedRoute, private location: Location){ constructor(public auth: Authentication, private router: Router){}
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(){ ngAfterViewInit(){
let ViewLinks = [ this.homeLink, this.mistLink, this.storeLink, this.aboutLink ]; let ViewLinks = [ this.homeLink, this.mistLink, this.storeLink, this.aboutLink ];
@@ -1,7 +1,15 @@
import { WebSiteData } from "./WebsiteData";
export class Account { export class Account {
public id: number | null = null; public id: number = -1;
public userName: string = ""; public userName: string = "";
public email: string = ""; public email: string = "";
public role: string = ""; public emailVerified: boolean = false;
public dataServer: string = ""; public passwordHash: string = "";
public siteData: WebSiteData = new WebSiteData();
public error: string = "";
constructor(init?: Partial<Account>) {
Object.assign(this, init);
}
} }
@@ -1,5 +1,5 @@
export class Product { export class Product {
public id: number | null = null; public id: number = -1;
public name: string = ""; public name: string = "";
public description: string = ""; public description: string = "";
public curShowingIMG: number = 0; public curShowingIMG: number = 0;
@@ -1,5 +1,5 @@
export class WebSiteData { export class WebSiteData {
public accountID: number | null = null; public accountID: number = -1;
public failedPasswordLock: boolean = false; public failedPasswordLock: boolean = false;
public passwordAttempts: number = 5; public passwordAttempts: number = 5;
public currentPasswordAttempts: number = 0; public currentPasswordAttempts: number = 0;
@@ -0,0 +1,23 @@
<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>
@@ -0,0 +1,55 @@
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);
}
});
}
}
@@ -0,0 +1,35 @@
<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>
@@ -0,0 +1,57 @@
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);
}
}
)
}
}
@@ -0,0 +1,27 @@
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(["/"]);
}
}
@@ -0,0 +1,35 @@
<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>
@@ -0,0 +1,85 @@
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);
}
});
}
}
@@ -0,0 +1,29 @@
<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>
@@ -0,0 +1,69 @@
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);
}
});
}
}
}
@@ -0,0 +1,29 @@
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() {
}
}
@@ -0,0 +1,8 @@
<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>
@@ -0,0 +1,54 @@
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);
}
});
}
}
@@ -0,0 +1,28 @@
<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&commat;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>
@@ -0,0 +1,19 @@
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 user is not Admin -> route home
if (auth.loggedInUser.role != "Admin"){ if (auth.loggedInUser.siteData.role != "Admin"){
router.navigate(["/"]); router.navigate(["/"]);
} }
// Load product // Load product
const formData = new FormData(); 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({ this.http.post<Product>( "api/product/get", formData ).subscribe({
next: async (data) => { next: async (data) => {
this.newItem = data; this.newItem = data;
@@ -28,7 +28,7 @@ export class NewItemComponent {
} }
// If user is not Admin -> route home // If user is not Admin -> route home
if (auth.loggedInUser.role != "Admin"){ if (auth.loggedInUser.siteData.role != "Admin"){
router.navigate(["/"]); router.navigate(["/"]);
} }
}; };
@@ -15,16 +15,16 @@
</div> </div>
<h2 class="gameCard-Price">${{ (product.cost/100).toFixed(2) }}</h2> <h2 class="gameCard-Price">${{ (product.cost/100).toFixed(2) }}</h2>
<button class="gameCard-Button" >Add To Cart</button> <button class="gameCard-Button" >Add To Cart</button>
<div *ngIf="auth.loggedInUser.role == 'Admin'"> <div *ngIf="auth.loggedInUser.siteData.role == 'Admin'">
<button style="width: calc(50% - 10px); margin: 5px;" [routerLink]="['/store/admin/edit']" [queryParams]="{ ProductID: product.id }" > <button style="width: calc(50% - 10px); margin: 5px;" [routerLink]="['/store/admin/edit']" [queryParams]="{ ProductID: product.id }" >
Edit Edit
</button> </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 Delete
</button> </button>
</div> </div>
</div> </div>
<div *ngIf="auth.loggedInUser.role == 'Admin'"> <div *ngIf="auth.loggedInUser.siteData.role == 'Admin'">
<button style="width: calc(100% - 10px); margin: 5px;" [routerLink]="['/store/admin/new']" > <button style="width: calc(100% - 10px); margin: 5px;" [routerLink]="['/store/admin/new']" >
New New
</button> </button>
@@ -1,39 +1,84 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { Account } from "../models/Account"; import { Account } from "../models/Account";
import { BehaviorSubject, Observable } from "rxjs"; import { BehaviorSubject, Observable } from "rxjs";
import { HttpClient } from "@angular/common/http"; import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http";
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class Authentication{ export class Authentication{
private _user = new BehaviorSubject<Account>( new Account ); private _user = new BehaviorSubject<Account>(this.getUserFromStorage());
user$ = this._user.asObservable(); user$ = this._user.asObservable();
constructor( private http: HttpClient){ } constructor( private http: HttpClient){ }
getLoginState(): Observable<Account> { Login(UserName: string, Password: string, StayLoggedIn: boolean): Observable<Account> {
let sub = this.http.post<Account>( "api/account/loginState", {}, {} );
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 } );
sub.subscribe({ sub.subscribe({
next: data => { next: data => {
this._user.next(data); if (data.error.length === 0){
this._user.next(data);
this.setUserToStorage(data, StayLoggedIn == true ? SessionType.Forever : SessionType.Session);
}
}, },
error: err => { error: err => {
console.log("No login state found: ", err.error); console.log("HTTP Error Signing In: ", err);
} }
}); });
return sub; return sub;
} }
Logout(){ Logout(){
this.http.post<Account>( "api/account/logout", {}, { responseType: 'json' } ).subscribe( );
this._user.next( new Account ); this._user.next( new Account );
return this.http.post<Account>( "api/account/logout", {}, { responseType: 'json' } ); this.delUserFromStorage();
} }
get isLoggedIn(): boolean { get isLoggedIn(): boolean {
return this._user.value.id != null ? true : false; return this._user.value.id != -1 ? true : false;
} }
get loggedInUser(): Account { get loggedInUser(): Account {
return this._user.value; 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,48 +1,284 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using MistoxWebsite.Server.Services;
using MistoxWebsite.Server.Services.DatabaseService; using MistoxWebsite.Server.Services.DatabaseService;
using MistoxWebsite.Server.Entities; using MistoxWebsite.Server.Entities;
using System.Text.Json;
using System.Text;
namespace MistoxWebsite.Server.Controllers { namespace MistoxWebsite.Server.Controllers {
[ApiController] [ApiController]
[Route("api/account/")] [Route("api/account/[controller]")]
public class AuthenticationController : MistoxControllerBase { public class AuthenticationController : MistoxControllerBase {
public AuthenticationController(DatabaseService db) : base(db) { } EmailService _emailContext;
[HttpPost("loginstate")] public AuthenticationController(DatabaseService db, EmailService emailContext) : base(db) {
public ActionResult<Account> LoginState() { _emailContext = emailContext;
if (isLoggedIn()) {
return Ok(getLoggedInUser());
}
return NotFound("Not logged in");
} }
[HttpPost("loginticket")] [Route("login")]
public async Task<ActionResult> LoginTicket([FromBody] string LoginToken) { [HttpPost]
using (HttpClient client = new HttpClient()) { public async Task<ActionResult<Account>> Login([FromForm] string UserName, [FromForm] string PasswordHash, [FromForm] bool StayLoggedIn) {
var payload = new { ticket = LoginToken }; try {
StringContent jsonPayload = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); Account? test = await _databaseService.GetAccount(UserName.ToLower());
HttpResponseMessage JWTResponse = await client.PostAsync("https://auth.mistox.com/api/auth/token", jsonPayload); if (test != null) {
if (JWTResponse.IsSuccessStatusCode) { if (test.EmailVerified == true) {
string JWT = await JWTResponse.Content.ReadAsStringAsync(); if (test.FailedPasswordLock) {
signIn(JWT); if (test.CurrentPasswordAttempts >= test.PasswordAttempts) {
return Ok(); return new Account() { Error = "Too many failed password attempts. Please reset your password" };
} else { }
string error = await JWTResponse.Content.ReadAsStringAsync(); }
return NotFound(error); 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 };
} }
} }
[HttpGet("logout")] [Route("register")]
public ActionResult Logout() { [HttpPost]
if (isLoggedIn()) { public async Task<ActionResult<Account>> Register([FromForm] string Email, [FromForm] string UserName, [FromForm] string PasswordHash) {
signOut(); try {
return Redirect("/"); 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 {
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;
}
}
[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 {
return Ok();
}
}
[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 {
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;
} }
return NotFound("Not logged in");
} }
} }
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using MistoxWebsite.Server.Entities; using MistoxWebsite.Server.Entities;
using MistoxWebsite.Server.Services.DatabaseService; using MistoxWebsite.Server.Services.DatabaseService;
@@ -13,19 +12,6 @@ namespace MistoxWebsite.Server.Controllers {
_databaseService = databaseService; _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() { public bool isLoggedIn() {
if (User.Identity != null && User.Identity.IsAuthenticated) { if (User.Identity != null && User.Identity.IsAuthenticated) {
return true; return true;
@@ -34,19 +20,16 @@ namespace MistoxWebsite.Server.Controllers {
} }
public int getLoggedInUserID() { public int getLoggedInUserID() {
return Convert.ToInt32(User.FindFirstValue(ClaimTypes.NameIdentifier)); return Convert.ToInt32(User.FindFirst("ID")?.Value);
} }
public Account getLoggedInUser() { public async Task<Account> getLoggedInUser() {
try { try {
Account building = new Account { Account? test = await _databaseService.GetAccount(getLoggedInUserID());
ID = Convert.ToInt32(User.FindFirstValue(ClaimTypes.NameIdentifier)), if (test != null) {
UserName = User.FindFirstValue(ClaimTypes.Name)!.ToString(), return test;
Email = User.FindFirstValue(ClaimTypes.Email)!.ToString(), }
Role = User.FindFirstValue(ClaimTypes.Role)!.ToString(), return new Account();
DataServer = User.FindFirstValue(ClaimTypes.UserData)!.ToString()
};
return building;
} catch { } catch {
return new Account(); return new Account();
} }
@@ -14,7 +14,7 @@ namespace MistoxWebsite.Server.Controllers {
public async Task<ActionResult<bool>> CreateProduct([FromForm] Product obj, [FromForm] IFormFile[] images) { public async Task<ActionResult<bool>> CreateProduct([FromForm] Product obj, [FromForm] IFormFile[] images) {
try { try {
if (isLoggedIn()) { if (isLoggedIn()) {
Account user = getLoggedInUser(); Account user = await getLoggedInUser();
if (user.Role == "Admin") { if (user.Role == "Admin") {
List<ProductImage> building = new List<ProductImage>(); List<ProductImage> building = new List<ProductImage>();
foreach (var file in images) { foreach (var file in images) {
@@ -70,7 +70,7 @@ namespace MistoxWebsite.Server.Controllers {
public async Task<ActionResult<bool>> DeleteProduct([FromForm] int productID) { public async Task<ActionResult<bool>> DeleteProduct([FromForm] int productID) {
try { try {
if (isLoggedIn()) { if (isLoggedIn()) {
Account user = getLoggedInUser(); Account user = await getLoggedInUser();
if (user.Role == "Admin") { if (user.Role == "Admin") {
await _databaseService.DeleteProduct(productID); await _databaseService.DeleteProduct(productID);
return true; return true;
@@ -3,11 +3,17 @@
namespace MistoxWebsite.Server.Entities { namespace MistoxWebsite.Server.Entities {
public class Account { public class Account {
public int? ID { get; set; } // PK public int ID { get; set; } // PK
public string UserName { get; set; } = ""; public string UserName { get; set; } = "";
public string Email { 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 Role { get; set; } = "Generic";
public string DataServer { get; set; } = ""; public string EmailToken { get; set; } = "";
public string Error { get; set; } = "";
} }
public class Product { public class Product {
@@ -11,7 +11,6 @@
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.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.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="Microsoft.AspNetCore.Mvc.WebApiCompatShim" Version="2.3.0" />
<PackageReference Include="MySql.Data" Version="9.2.0" /> <PackageReference Include="MySql.Data" Version="9.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
+15 -95
View File
@@ -1,10 +1,4 @@
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.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using MistoxWebsite.Server.Controllers.Payment; using MistoxWebsite.Server.Controllers.Payment;
using MistoxWebsite.Server.Services; using MistoxWebsite.Server.Services;
using MistoxWebsite.Server.Services.DatabaseService; using MistoxWebsite.Server.Services.DatabaseService;
@@ -84,97 +78,25 @@ if (IPayment._PaymentType == PaymentType.StripeIntent) {
IPayment._EndpointSecret = string.IsNullOrEmpty(StripeEndpointKey) ? "" : StripeEndpointKey; IPayment._EndpointSecret = string.IsNullOrEmpty(StripeEndpointKey) ? "" : StripeEndpointKey;
} }
//////////////////////////////// // Authentication Service
/////// Auth Service //////// builder.Services.AddAuthentication( options => {
//////////////////////////////// options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
} ).AddCookie(options => {
RsaSecurityKey? PublicKey = null; options.Cookie.HttpOnly = true;
using (HttpClient client = new HttpClient()) { options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
while (PublicKey == null) { options.Cookie.SameSite = SameSiteMode.Strict;
HttpResponseMessage PublicKeyResponse = await client.GetAsync("https://auth.mistox.com/api/auth/publickey"); options.LoginPath = "/account/login";
if (PublicKeyResponse.IsSuccessStatusCode) { options.LogoutPath = "/account/logout";
string publicKey = await PublicKeyResponse.Content.ReadAsStringAsync(); options.SlidingExpiration = true;
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 => {
/// Rate Limiting Service //// builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); // No CORS
//////////////////////////////// } ) );
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.AddControllers();
builder.Services.AddRazorPages();
var app = builder.Build(); var app = builder.Build();
@@ -186,8 +108,6 @@ if( !app.Environment.IsDevelopment() ) {
app.UseDefaultFiles(); app.UseDefaultFiles();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseRateLimiter();
app.UseCors(); app.UseCors();
app.UseRouting(); app.UseRouting();
@@ -0,0 +1,160 @@
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();
}
}
}
}