diff --git a/.editorconfig b/.editorconfig index 07820a4..e044e0c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,4 +13,5 @@ trim_trailing_whitespace = false 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 \ No newline at end of file +csharp_new_line_after_else = false +csharp_new_line_before_else = false \ No newline at end of file diff --git a/.env_Template b/.env_Template index 98f082d..dee9450 100755 --- a/.env_Template +++ b/.env_Template @@ -1,13 +1,32 @@ -Payment_Service=StripeIntent # Options are [ StripeIntent ] +############# +## Payment ## +############# +# Options are [ StripeIntent ] +Payment_Service=StripeIntent + +# StripeIntent Options Stripe_PublicKey= Stripe_PublicKey= Stripe_Endpoint_Secret= -MySQL_Server=mistox-database +#################### +## Authentication ## +#################### + +# Random secret token for encrypting JWT contents +JWT_Secret= + +############## +## Database ## +############## + MySQL_User=root -MySQL_Database=mistox -MySQL_Pass=oasv34$8gpv023dd # Random value for the server and MySQL to communicate with +MySQL_Pass=oasv34$8gpv023dd + +############## +## Email ## +############## Email_Server= # Hostname of email server Email_Port= # SMTP port used diff --git a/ToDo.yaml b/ToDo.yaml index ca253bb..e5128e5 100755 --- a/ToDo.yaml +++ b/ToDo.yaml @@ -9,10 +9,30 @@ Server: Client: jobs/new: - When remote job is check'd it still asks for location information Want to add Required skills to help with filtering - Need to fix some UI bugs. + When enter is pressed it tries to submit the form + Should run the whole carosel on enter before the submit is sent + Need to validate input before allowing next step Want to add completed job listing preview at end of carosel database: - Add Applied Jobs Table \ No newline at end of file + Add Applied Jobs Table + + + +Task: + Block API Access as much as possible [ Rate limit | Auth Req | CORS | Disallow AI keyword filters ] + Resume builder minimal user input [ Dont allow AI input ] + Auto unlist jobs after a month of no activity [ Multiple offenders marked ] + Dont allow external applications for users on company sites from the start + Allow company to look up users if their resume is public [ Maybe auto with notify ] + Allow users to look up jobs and apply [ Boost visibility | Completely manual ] + Allow multiple resume's for job specific work + Mark ghost listings to allow users to be informed and put companies on blast + Create advanced filtering tools for company lookup and resume lookup + + Create and Auth Database based on the docker compose + Create a server table inside the auth database + Point all requests after auth to the correct regional server. -> Currently only Mistox-West exists + + CompanyConnect | need to lookup company before making a new one \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3ec7754..4098a89 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,18 +5,19 @@ services: image: docker.mistox.net/boredcareers-website:latest restart: always environment: + - MySQLServer=boredcareers-database + - MySQLDatabase=boredcareers - PaymentService=${Payment_Service} - StripePublicKey=${Stripe_PublicKey} - StripeApiKey=${Stripe_ApiKey} - StripeEndpointSecret=&{Stripe_Endpoint_Secret} - - MySQLServer=${MySQL_Server} - MySQLUser=${MySQL_User} - MySQLPass=${MySQL_Pass} - - MySQLDatabase=${MySQL_Database} - EmailServer=${Email_Server} - EmailPort=${Email_Port} - EmailAddress=${Email_Address} - EmailPassword=${Email_Password} + - JWTsecret=${JWT_Secret} ports: - 5000:5000 depends_on: diff --git a/src/Client/src/app/pages/account/logout/logout.component.ts b/src/Client/src/app/pages/account/logout/logout.component.ts index 4357ff9..130ee39 100644 --- a/src/Client/src/app/pages/account/logout/logout.component.ts +++ b/src/Client/src/app/pages/account/logout/logout.component.ts @@ -23,7 +23,7 @@ export class LogoutComponent { ngAfterViewInit(){ this.auth.Logout().subscribe({ next: data => { - this.router.navigate(["/"]); + window.location.href = ""; } }); } diff --git a/src/Client/src/app/pages/main/company/connect/companyconnect.component.css b/src/Client/src/app/pages/main/company/connect/companyconnect.component.css index 4203dc9..afcf5dc 100644 --- a/src/Client/src/app/pages/main/company/connect/companyconnect.component.css +++ b/src/Client/src/app/pages/main/company/connect/companyconnect.component.css @@ -1,3 +1,21 @@ +button { + width: 150px; + border-radius: 5px; + margin: 10px; + text-align: center; + padding: 15px 0; + transition: .5s; + background-color: #0000; + border: 1px solid var(--Mistox-White); + color: var(--Mistox-White); + text-decoration: none; +} + + button:hover { + background-color: #00000044; + color: var(--Mistox-Light); + } + .title-text { width: 100%; text-align: center; diff --git a/src/Client/src/app/pages/main/company/connect/companyconnect.component.html b/src/Client/src/app/pages/main/company/connect/companyconnect.component.html index 12111ca..9b71c56 100644 --- a/src/Client/src/app/pages/main/company/connect/companyconnect.component.html +++ b/src/Client/src/app/pages/main/company/connect/companyconnect.component.html @@ -7,13 +7,12 @@
- +
@@ -23,14 +22,12 @@
- +
@@ -39,13 +36,13 @@
- - + +
@@ -54,45 +51,54 @@
-
-
- - -
-
- - +
+
+
+ + +
+
+ + +
+ + +
+ - -
-

Job Location

-
-
- - -
-
- - -
-
- - -
-
- - +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
@@ -101,10 +107,13 @@
- +
+
@@ -134,14 +143,17 @@ postal code: {{ newListing.postalCode }}
-
- {{ newListing.description }} +
+ {{ descLine }}
+
\ No newline at end of file diff --git a/src/Client/src/app/pages/main/company/connect/companyconnect.component.ts b/src/Client/src/app/pages/main/company/connect/companyconnect.component.ts index 92b8124..25bde87 100644 --- a/src/Client/src/app/pages/main/company/connect/companyconnect.component.ts +++ b/src/Client/src/app/pages/main/company/connect/companyconnect.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, QueryList, ViewChildren } from '@angular/core'; +import { Component, ElementRef, HostListener, QueryList, ViewChildren } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { Router, ActivatedRoute, RouterModule } from '@angular/router'; @@ -26,6 +26,20 @@ export class CompanyConnectComponent { }; ngAfterViewInit(){ + this.formSteps.changes.subscribe(() => { + this.updateUI(0); + }); + this.updateUI(0); + } + + @HostListener('window:keydown', ['$event']) + handleGlobalKeyDown(event: KeyboardEvent){ + if (event.key === 'Tab'){ + event.preventDefault(); + } + } + + updateUI(subItem: number){ this.formSteps.forEach((step: ElementRef, i: number) => { if (i === this.currentStep) { step.nativeElement.style.left = '0%'; @@ -35,35 +49,82 @@ export class CompanyConnectComponent { step.nativeElement.style.left = '100%'; } }); + setTimeout(() => { + (this.formSteps.get(this.currentStep)?.nativeElement.querySelectorAll('.input-field')[subItem] as HTMLElement)?.focus(); + }, 500); } nextStep(){ this.currentStep += 1; - this.formSteps.forEach((step: ElementRef, i: number) => { - if (i === this.currentStep) { - step.nativeElement.style.left = '0%'; - } else if (i < this.currentStep) { - step.nativeElement.style.left = '-100%'; - } else { - step.nativeElement.style.left = '100%'; - } - }); + this.updateUI(0); } prevStep(){ this.currentStep -= 1; - this.formSteps.forEach((step: ElementRef, i: number) => { - if (i === this.currentStep) { - step.nativeElement.style.left = '0%'; - } else if (i < this.currentStep) { - step.nativeElement.style.left = '-100%'; - } else { - step.nativeElement.style.left = '100%'; - } - }); + this.updateUI(0); + } + + isNullOrEmpty(str: string | null | undefined): boolean { + return !str || str.trim().length === 0; + } + + focusFrame(frameNum: number, subItem: number): void { + this.currentStep = frameNum; + this.updateUI(subItem); } PostNewCompany(company: Company){ + + if (this.isNullOrEmpty(company.name)){ + this.focusFrame(0, 0); + return; + } + + if (this.isNullOrEmpty(company.websiteURL)){ + this.focusFrame(1, 0); + return; + } + + if (this.isNullOrEmpty(company.logoURL)){ + this.focusFrame(2, 0); + return; + } + + if (this.isNullOrEmpty(company.email)){ + this.focusFrame(3, 0); + return; + } + + if (this.isNullOrEmpty(company.phone)){ + this.focusFrame(3, 1); + return; + } + + if (this.isNullOrEmpty(company.city)){ + this.focusFrame(4, 0); + return; + } + + if (this.isNullOrEmpty(company.country)){ + this.focusFrame(4, 1); + return; + } + + if (this.isNullOrEmpty(company.stateOrRegion)){ + this.focusFrame(4, 2); + return; + } + + if (this.isNullOrEmpty(company.postalCode)){ + this.focusFrame(4, 3); + return; + } + + if (this.isNullOrEmpty(company.description)){ + this.focusFrame(5, 0); + return; + } + this.http.post("api/company?newCompany=true", company).subscribe({ next: data => { this.router.navigate([""]); diff --git a/src/Client/src/app/pages/main/jobs/jobs.component.css b/src/Client/src/app/pages/main/jobs/jobs.component.css index 58f3098..6044df9 100644 --- a/src/Client/src/app/pages/main/jobs/jobs.component.css +++ b/src/Client/src/app/pages/main/jobs/jobs.component.css @@ -1,3 +1,21 @@ +button { + width: 150px; + border-radius: 5px; + margin: 10px; + text-align: center; + padding: 15px 0; + transition: .5s; + background-color: #0000; + border: 1px solid var(--Mistox-White); + color: var(--Mistox-White); + text-decoration: none; +} + + button:hover { + background-color: #00000044; + color: var(--Mistox-Light); + } + .full-width { display: block; width: 100%; @@ -5,14 +23,65 @@ } .tile-frame { - column-count: 4; + display: grid; + grid-template-columns: repeat(4, 1fr); column-gap: 20px; padding: 20px; width: calc(100% - 40px); } .tile{ - background-color: var(--Mistox-Dark)\); - height: 40px; + background-color: var(--Mistox-Dark); + color: var(--Mistox-White); break-inside: avoid; + padding: 20px; + border-radius: 20px; + margin-bottom: 20px; +} + +.jobs-frame { + width: 100%; + background-color: #8888; + border-top: 2px solid black; +} + +.post-job-frame { + display: flex; + justify-content: center; + padding: 10px 0; +} + +.tile-title { + text-align: center; + border-bottom: 1px solid; +} + +.tile-title h1 { + font-size: 40px; + margin: 5px 0; +} + +.tile-title h2 { + font-size: 14px; +} + +.tile-split { + columns: 2; + text-align: center; + padding: 10px 0; +} + +.tile-split h1 { + margin: 0; +} + +.tile-button { + display: flex; + width: 100%; + justify-content: center; +} + +.post-job-frame button { + border-color: var(--Mistox-Black); + color: var(--Mistox-Black); } \ No newline at end of file diff --git a/src/Client/src/app/pages/main/jobs/jobs.component.html b/src/Client/src/app/pages/main/jobs/jobs.component.html index f3c49a0..5e5ab3d 100644 --- a/src/Client/src/app/pages/main/jobs/jobs.component.html +++ b/src/Client/src/app/pages/main/jobs/jobs.component.html @@ -1,5 +1,5 @@ -
+

{{ cur.title }}

@@ -11,14 +11,13 @@

{{ cur.stateOrRegion }}

{{ cur.country }}

{{ cur.postalCode }}

-

{{ cur.description }}

Posted: {{ cur.createdTime }}

Modified: {{ cur.modifiedTime }}

-
+
@@ -26,17 +25,20 @@
-

{{ cur.title }}

-

{{ cur.jobType }}

-

Is Remote: {{ cur.remote }}

-

{{ cur.salaryMin }}

-

{{ cur.salaryMax }}

-

{{ cur.city }}

-

{{ cur.stateOrRegion }}

-

{{ cur.country }}

-

{{ cur.postalCode }}

-

{{ cur.description }}

-

Posted: {{ cur.createdTime }}

-

Modified: {{ cur.modifiedTime }}

+
+

{{ cur.title }}

+

${{ cur.salaryMax }} - ${{ cur.salaryMin }}

+
+
+

{{ cur.jobType }}

+

Remote

+
+
+

{{ cur.city }}

+

{{ cur.stateOrRegion }}

+
+
+ +
\ No newline at end of file diff --git a/src/Client/src/app/pages/main/jobs/new/jobnew.component.css b/src/Client/src/app/pages/main/jobs/new/jobnew.component.css index 3606281..4203dc9 100644 --- a/src/Client/src/app/pages/main/jobs/new/jobnew.component.css +++ b/src/Client/src/app/pages/main/jobs/new/jobnew.component.css @@ -14,7 +14,7 @@ form { .center { width: 100%; - display: flex; + display: grid; justify-content: center; } @@ -31,6 +31,15 @@ form { border-radius: 10px; padding: 40px; break-inside: avoid; + margin-bottom: 20px; +} + +.footer-frame { + background-color: #00000044; + border-radius: 10px; + padding: 10px; + break-inside: avoid; + margin-bottom: 20px; } .split { diff --git a/src/Client/src/app/pages/main/jobs/new/jobnew.component.html b/src/Client/src/app/pages/main/jobs/new/jobnew.component.html index d6e9e90..c519c73 100644 --- a/src/Client/src/app/pages/main/jobs/new/jobnew.component.html +++ b/src/Client/src/app/pages/main/jobs/new/jobnew.component.html @@ -9,11 +9,16 @@
+
@@ -36,7 +41,6 @@
+
+
+
+ + +
+
+ + +
-
- - -
-
- - -
-
- - +
+
+ + +
+
+ + +
diff --git a/src/Client/src/app/pages/main/jobs/new/jobnew.component.ts b/src/Client/src/app/pages/main/jobs/new/jobnew.component.ts index 793424d..62324e2 100644 --- a/src/Client/src/app/pages/main/jobs/new/jobnew.component.ts +++ b/src/Client/src/app/pages/main/jobs/new/jobnew.component.ts @@ -42,6 +42,13 @@ export class JobNewComponent { }; ngAfterViewInit(){ + this.formSteps.changes.subscribe(() => { + this.updateUI(); + }); + this.updateUI(); + } + + updateUI(){ this.formSteps.forEach((step: ElementRef, i: number) => { if (i === this.currentStep) { step.nativeElement.style.left = '0%'; @@ -55,28 +62,12 @@ export class JobNewComponent { nextStep(){ this.currentStep += 1; - this.formSteps.forEach((step: ElementRef, i: number) => { - if (i === this.currentStep) { - step.nativeElement.style.left = '0%'; - } else if (i < this.currentStep) { - step.nativeElement.style.left = '-100%'; - } else { - step.nativeElement.style.left = '100%'; - } - }); + this.updateUI(); } prevStep(){ this.currentStep -= 1; - this.formSteps.forEach((step: ElementRef, i: number) => { - if (i === this.currentStep) { - step.nativeElement.style.left = '0%'; - } else if (i < this.currentStep) { - step.nativeElement.style.left = '-100%'; - } else { - step.nativeElement.style.left = '100%'; - } - }); + this.updateUI(); } PostJobListing(jobListing: JobListing){ diff --git a/src/Client/src/app/services/Authentication.ts b/src/Client/src/app/services/Authentication.ts index 138ccf7..60803ba 100644 --- a/src/Client/src/app/services/Authentication.ts +++ b/src/Client/src/app/services/Authentication.ts @@ -24,6 +24,7 @@ export class Authentication{ let sub = this.http.post( "api/account/login", body, { headers } ); sub.subscribe({ next: data => { + data.passwordHash = ""; this._user.next(data); this.setUserToStorage(data, StayLoggedIn == true ? SessionType.Forever : SessionType.Session); }, diff --git a/src/Server/Controllers/AuthenticationController.cs b/src/Server/Controllers/AuthenticationController.cs index 994ddc1..81fcf06 100755 --- a/src/Server/Controllers/AuthenticationController.cs +++ b/src/Server/Controllers/AuthenticationController.cs @@ -1,7 +1,4 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; +using Microsoft.AspNetCore.Mvc; using BoredCareers.Services; using BoredCareers.Services.DatabaseService; using BoredCareers.Entities; @@ -34,19 +31,9 @@ namespace BoredCareers.Controllers { test.CurrentPasswordAttempts = 0; await _databaseService.SetAccount(test); - List claims = new List() { - new Claim("ID", test.ID.ToString()), - new Claim(ClaimTypes.NameIdentifier, test.ID.ToString()) - }; + string jwt = BoredCareersJWT.GenereateJWTToken(test.ID, StayLoggedIn); + BoredCareersJWT.SignIn(Response, StayLoggedIn, jwt); - 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 Ok(test); } else { test.CurrentPasswordAttempts += 1; @@ -151,9 +138,9 @@ namespace BoredCareers.Controllers { [Route("logout")] [HttpPost] - public async Task Logout() { + public ActionResult Logout() { if (isLoggedIn()) { - await HttpContext.SignOutAsync(); + BoredCareersJWT.SignOut(Response); return Ok(); } return NotFound(); diff --git a/src/Server/Controllers/MistoxControllerBase.cs b/src/Server/Controllers/MistoxControllerBase.cs index 1253b5c..eb502ca 100644 --- a/src/Server/Controllers/MistoxControllerBase.cs +++ b/src/Server/Controllers/MistoxControllerBase.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using BoredCareers.Entities; using BoredCareers.Services.DatabaseService; +using System.Security.Claims; namespace BoredCareers.Controllers { @@ -20,7 +21,7 @@ namespace BoredCareers.Controllers { } public int getLoggedInUserID() { - return Convert.ToInt32(User.FindFirst("ID")?.Value); + return Convert.ToInt32(User.FindFirstValue(ClaimTypes.NameIdentifier)); } public async Task getLoggedInUser() { diff --git a/src/Server/Controllers/PaymentController.cs b/src/Server/Controllers/PaymentController.cs index 31e0537..f4c24c1 100755 --- a/src/Server/Controllers/PaymentController.cs +++ b/src/Server/Controllers/PaymentController.cs @@ -28,7 +28,7 @@ namespace BoredCareers.Controllers { public async Task paymentWebhook() { try { string body = await new StreamReader(Request.Body).ReadToEndAsync(); - await _paymentService.ValidatePurchase(body, Request.Headers["Stripe-Signature"].ToString()); + _paymentService.ValidatePurchase(body, Request.Headers["Stripe-Signature"].ToString()); return Ok(); } catch (Exception ex) { return NotFound(ex.ToString()); diff --git a/src/Server/Controllers/PaymentMethods/IPayment.cs b/src/Server/Controllers/PaymentMethods/IPayment.cs index 73196d3..ae9c4be 100644 --- a/src/Server/Controllers/PaymentMethods/IPayment.cs +++ b/src/Server/Controllers/PaymentMethods/IPayment.cs @@ -1,5 +1,3 @@ -using BoredCareers.Entities; - namespace BoredCareers.Controllers.Payment { public interface IPayment { @@ -8,7 +6,7 @@ namespace BoredCareers.Controllers.Payment { public static string _EndpointSecret = ""; public static string _PublicKey = ""; - public Task ValidatePurchase(string WebHookData, string Headers); + public void ValidatePurchase(string WebHookData, string Headers); } diff --git a/src/Server/Controllers/PaymentMethods/StripeIntents.cs b/src/Server/Controllers/PaymentMethods/StripeIntents.cs index fca5a5e..ddb6294 100644 --- a/src/Server/Controllers/PaymentMethods/StripeIntents.cs +++ b/src/Server/Controllers/PaymentMethods/StripeIntents.cs @@ -11,7 +11,7 @@ namespace BoredCareers.Controllers { _databaseService = databaseService; } - public async Task ValidatePurchase(string WebHookData, string Headers) { + public void ValidatePurchase(string WebHookData, string Headers) { Stripe.Event e = Stripe.EventUtility.ConstructEvent( WebHookData, Headers, IPayment._EndpointSecret ); if (e.Type == "payment_intent.succeeded") { // Extract Data from payment confirm diff --git a/src/Server/Program.cs b/src/Server/Program.cs index d4396af..d5beb5c 100755 --- a/src/Server/Program.cs +++ b/src/Server/Program.cs @@ -1,10 +1,13 @@ -using Microsoft.AspNetCore.Authentication.Cookies; using BoredCareers.Controllers.Payment; using BoredCareers.Services; using BoredCareers.Services.DatabaseService; using System.Threading.RateLimiting; using Stripe; using System.Security.Claims; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Text; var builder = WebApplication.CreateBuilder(args); @@ -36,6 +39,15 @@ string dbPass = !string.IsNullOrEmpty(_dbpass) ? _dbpass : "oasv34$8gpv023dd"; DatabaseService databaseService = new DatabaseService(connectionString: "server=" + dbserver + ";user=" + dbUser + ";database=" + dbdatabase + ";password=" + dbPass + ";port=3306;"); builder.Services.Add( new ServiceDescriptor( typeof( DatabaseService ), databaseService ) ); +//////////////////////////////// +////////// Auth Service //////// +//////////////////////////////// + +// Address +string? _jwtSecret = Environment.GetEnvironmentVariable("JWTsecret"); +string JWTsecret = !string.IsNullOrEmpty(_jwtSecret) ? _jwtSecret : "v0Ftluhdh7Nht8^2b5eaiC^IS^VS1ku0VBs3j*B2"; +BoredCareersJWT.TokenSecretKey = JWTsecret; + //////////////////////////////// ///////// Email Service //////// //////////////////////////////// @@ -81,15 +93,40 @@ if (IPayment._PaymentType == PaymentType.StripeIntent) { } // 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; +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 = BoredCareersJWT.TokenIssuer, + ValidAudience = BoredCareersJWT.TokenAudience, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(BoredCareersJWT.TokenSecretKey)), + ClockSkew = TimeSpan.FromMinutes(1) + }; + options.Events = new JwtBearerEvents { + OnMessageReceived = context => { + context.Token = context.Request.Cookies[BoredCareersJWT.TokenName]; + 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)) { + int accountID = Convert.ToInt32(context.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value); + bool isPersistent = bool.Parse(context.Principal?.FindFirst(ClaimTypes.IsPersistent)?.Value); + var newJWT = BoredCareersJWT.GenereateJWTToken(accountID, isPersistent); + BoredCareersJWT.SignIn(context.HttpContext.Response, isPersistent, newJWT); + } + } + return Task.CompletedTask; + } + }; }); builder.Services.AddCors(o => o.AddDefaultPolicy(builder => { diff --git a/src/Server/Server.csproj b/src/Server/Server.csproj index 621ce3d..c14307e 100755 --- a/src/Server/Server.csproj +++ b/src/Server/Server.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Server/Services/EmailService/ResetPasswordEmail.cs b/src/Server/Services/EmailService/ResetPasswordEmail.cs index 9481aeb..7eff79c 100755 --- a/src/Server/Services/EmailService/ResetPasswordEmail.cs +++ b/src/Server/Services/EmailService/ResetPasswordEmail.cs @@ -1,5 +1,3 @@ -using System.Net.Mail; - namespace BoredCareers.Services { public partial class EmailService { diff --git a/src/Server/Services/EmailService/VerifyEmail.cs b/src/Server/Services/EmailService/VerifyEmail.cs index 575bf08..9207038 100755 --- a/src/Server/Services/EmailService/VerifyEmail.cs +++ b/src/Server/Services/EmailService/VerifyEmail.cs @@ -1,5 +1,3 @@ -using System.Net.Mail; - namespace BoredCareers.Services { public partial class EmailService { diff --git a/src/Server/Services/jwt.cs b/src/Server/Services/jwt.cs new file mode 100644 index 0000000..21dcf33 --- /dev/null +++ b/src/Server/Services/jwt.cs @@ -0,0 +1,57 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Microsoft.IdentityModel.Tokens; + +namespace BoredCareers.Services { + public class BoredCareersJWT { + + public static string TokenAudience = "mistox-llc-auth-token"; + public static string TokenIssuer = "https://auth.mistox.com"; + public static string TokenSecretKey = ""; + public static string TokenName = "mistox_session"; + + public static string GenereateJWTToken(int accountID, bool StayLoggedIn) { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.UTF8.GetBytes(TokenSecretKey); + + var tokenDiscriptor = new SecurityTokenDescriptor { + Subject = new ClaimsIdentity([ + new Claim(ClaimTypes.NameIdentifier, accountID.ToString()), + new Claim(ClaimTypes.IsPersistent, StayLoggedIn.ToString()) + ]), + Expires = DateTime.UtcNow.AddDays(7), + IssuedAt = DateTime.UtcNow, + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256), + Audience = TokenAudience, + Issuer = TokenIssuer + }; + + var token = tokenHandler.CreateToken(tokenDiscriptor); + return tokenHandler.WriteToken(token); + } + + public static void SignIn(HttpResponse Response, bool StayLoggedIn, string jwt) { + if (StayLoggedIn) { + // Stay logged in cookie + Response.Cookies.Append(TokenName, jwt, new CookieOptions { + Secure = true, + HttpOnly = true, + SameSite = SameSiteMode.Strict, + Expires = DateTime.UtcNow.AddDays(7) + }); + } else { + // Session cookie + Response.Cookies.Append(TokenName, jwt, new CookieOptions { + Secure = true, + HttpOnly = true, + SameSite = SameSiteMode.Strict, + }); + } + } + + public static void SignOut(HttpResponse Response) { + Response.Cookies.Delete(TokenName); + } + } +} \ No newline at end of file