aboutsummaryrefslogtreecommitdiff
path: root/Backend/Api
diff options
context:
space:
mode:
Diffstat (limited to 'Backend/Api')
-rw-r--r--Backend/Api/.gitignore454
-rw-r--r--Backend/Api/Api.sln25
-rw-r--r--Backend/Api/Api/Api.csproj18
-rw-r--r--Backend/Api/Api/Controllers/AuthController.cs77
-rw-r--r--Backend/Api/Api/Database/DatabaseConnection.cs11
-rw-r--r--Backend/Api/Api/Interfaces/IDatabaseConnection.cs9
-rw-r--r--Backend/Api/Api/Interfaces/IJwtService.cs13
-rw-r--r--Backend/Api/Api/Interfaces/IUserService.cs26
-rw-r--r--Backend/Api/Api/Models/User.cs51
-rw-r--r--Backend/Api/Api/Program.cs78
-rw-r--r--Backend/Api/Api/Properties/launchSettings.json31
-rw-r--r--Backend/Api/Api/Services/JwtService.cs130
-rw-r--r--Backend/Api/Api/Services/UserService.cs271
-rw-r--r--Backend/Api/Api/appsettings.Development.json8
-rw-r--r--Backend/Api/Api/appsettings.json26
15 files changed, 1228 insertions, 0 deletions
diff --git a/Backend/Api/.gitignore b/Backend/Api/.gitignore
new file mode 100644
index 0000000..2afa2e2
--- /dev/null
+++ b/Backend/Api/.gitignore
@@ -0,0 +1,454 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
diff --git a/Backend/Api/Api.sln b/Backend/Api/Api.sln
new file mode 100644
index 0000000..006fcf6
--- /dev/null
+++ b/Backend/Api/Api.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32825.248
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "Api\Api.csproj", "{98D1F700-C988-4F19-89D1-9B7D002D702D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {98D1F700-C988-4F19-89D1-9B7D002D702D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {98D1F700-C988-4F19-89D1-9B7D002D702D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98D1F700-C988-4F19-89D1-9B7D002D702D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {98D1F700-C988-4F19-89D1-9B7D002D702D}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {E6AD5655-69C9-485B-9F63-FF2F67F42B51}
+ EndGlobalSection
+EndGlobal
diff --git a/Backend/Api/Api/Api.csproj b/Backend/Api/Api/Api.csproj
new file mode 100644
index 0000000..93e31b7
--- /dev/null
+++ b/Backend/Api/Api/Api.csproj
@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <ImplicitUsings>enable</ImplicitUsings>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.10" />
+ <PackageReference Include="MongoDB.Driver" Version="2.18.0" />
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
+ <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.24.0" />
+ <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
+ <PackageReference Include="MailKit" Version="3.4.2" />
+ </ItemGroup>
+
+</Project>
diff --git a/Backend/Api/Api/Controllers/AuthController.cs b/Backend/Api/Api/Controllers/AuthController.cs
new file mode 100644
index 0000000..d835d97
--- /dev/null
+++ b/Backend/Api/Api/Controllers/AuthController.cs
@@ -0,0 +1,77 @@
+using Api.Interfaces;
+using Api.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Api.Controllers
+{
+ [Route("api/auth/")]
+ public class AuthController : Controller
+ {
+ private readonly IUserService _userService;
+ public AuthController(IUserService userService)
+ {
+ _userService = userService;
+ }
+
+ [HttpPost("register")]
+ public async Task<ActionResult<string>> Register([FromBody] Register creds)
+ {
+ //this is beyond scuffed and will be cleaned up later, when users,login and controllers are made
+ User novi = new User();
+ novi.email = creds.email;
+ novi.password = creds.password;
+ novi.username = creds.username;
+ novi.name = creds.name;
+ novi.verified = true;
+ novi.creationDate = DateTime.Now.ToUniversalTime();
+ novi._id = "";
+
+ int ret= await _userService.createUser(novi);
+ if (ret == -1)
+ return BadRequest("email already exists");
+ if (ret == -2)
+ return BadRequest("username already exists");
+
+ return Ok();
+ }
+ [HttpPost("login")]
+ public async Task<ActionResult<string>> Login([FromBody] Login creds)
+ {
+ var id = await _userService.UserIdFromJwt();
+ if (id != null) return Forbid();
+
+ var jwt= await _userService.Login(creds);
+ if (jwt != null)
+ {
+ return Ok(jwt);
+ }
+ return BadRequest("Pogresno uneti podaci");
+ }
+ [HttpPost("registeractual")]
+ public async Task<ActionResult<string>> RegisterActual([FromBody] Register creds)
+ {
+ var msg = await _userService.Register(creds);
+ if (msg == "Email Exists")
+ return Forbid(msg);
+ if (msg == "Username Exists")
+ return Forbid(msg);
+ return Ok(msg);
+ }
+ [HttpPost("verify")]
+ public async Task<ActionResult<string>> VerifyEmail([FromBody] VerifyUser creds)
+ {
+ var uspeh = await _userService.VerifyUser(creds);
+ if (!uspeh)
+ return BadRequest("Kod netacan ili istekao");
+ return Ok("Uspesno verifikovan");
+ }
+ [HttpPost("resetpass")]
+ public async Task<ActionResult<string>> ResetPass([FromBody] ResetPass creds)
+ {
+ var uspeh = await _userService.ResetPassword(creds);
+ if (!uspeh)
+ return BadRequest("Kod netacan ili istekao");
+ return Ok("Sifra uspesno resetovana");
+ }
+ }
+}
diff --git a/Backend/Api/Api/Database/DatabaseConnection.cs b/Backend/Api/Api/Database/DatabaseConnection.cs
new file mode 100644
index 0000000..65f4f52
--- /dev/null
+++ b/Backend/Api/Api/Database/DatabaseConnection.cs
@@ -0,0 +1,11 @@
+using Api.Interfaces;
+
+namespace Api.Database
+{
+ public class DatabaseConnection : IDatabaseConnection
+ {
+ public string ConnectionString { get; set; } = String.Empty;
+ public string DatabaseName { get; set; } = String.Empty;
+ public string UserCollectionName { get; set; } = String.Empty;
+ }
+}
diff --git a/Backend/Api/Api/Interfaces/IDatabaseConnection.cs b/Backend/Api/Api/Interfaces/IDatabaseConnection.cs
new file mode 100644
index 0000000..8938127
--- /dev/null
+++ b/Backend/Api/Api/Interfaces/IDatabaseConnection.cs
@@ -0,0 +1,9 @@
+namespace Api.Interfaces
+{
+ public interface IDatabaseConnection
+ {
+ string ConnectionString { get; set; }
+ string DatabaseName { get; set; }
+ string UserCollectionName { get; set; }
+ }
+}
diff --git a/Backend/Api/Api/Interfaces/IJwtService.cs b/Backend/Api/Api/Interfaces/IJwtService.cs
new file mode 100644
index 0000000..6274bf9
--- /dev/null
+++ b/Backend/Api/Api/Interfaces/IJwtService.cs
@@ -0,0 +1,13 @@
+using Api.Models;
+
+namespace Api.Interfaces
+{
+ public interface IJwtService
+ {
+ string GenToken(User user);
+ string TokenToId(string token);
+ public string GenEmailToken(User user);
+ public string EmailTokenToId(string token);
+ public string EmailTokenToKod(string token);
+ }
+} \ No newline at end of file
diff --git a/Backend/Api/Api/Interfaces/IUserService.cs b/Backend/Api/Api/Interfaces/IUserService.cs
new file mode 100644
index 0000000..5205028
--- /dev/null
+++ b/Backend/Api/Api/Interfaces/IUserService.cs
@@ -0,0 +1,26 @@
+using Api.Models;
+
+namespace Api.Interfaces
+{
+ public interface IUserService
+ {
+ Task<int> createUser(User user);
+ Task<List<User>> getUsers();
+ Task<User> getUserByEmail(String email);
+ Task<User> getUserByUsername(String username);
+ Task<long> updateUser(User user);
+ Task<User> deleteUser(String email);
+ Task<User> getUserById(string id);
+
+ Task<string> RenewToken(string existingToken);
+ Task<string> Login(Login login);
+ Task<string> Register(Register register);
+ Task<Boolean> VerifyUser(VerifyUser login);
+ Task<string> UserIdFromJwt();
+ Task<Boolean> ResendVerifyKod(Login login);
+ Boolean SendEmailKod(User user);
+ Task<Boolean> ForgotPassword(JustMail jm);
+ Task<Boolean> ResetPassword(ResetPass rp);
+
+ }
+}
diff --git a/Backend/Api/Api/Models/User.cs b/Backend/Api/Api/Models/User.cs
new file mode 100644
index 0000000..a1fe149
--- /dev/null
+++ b/Backend/Api/Api/Models/User.cs
@@ -0,0 +1,51 @@
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace Api.Models
+{
+ public class User
+ {
+ [BsonId]
+ [BsonRepresentation(BsonType.ObjectId)]
+ public String _id { get; set; }
+ public String name { get; set; }
+ public String username { get; set; }
+ public String email { get; set; }
+ public String emailToken { get; set; }
+ public Boolean verified { get; set; }
+ public String password { get; set; }
+ public DateTime creationDate { get; set; }
+ }
+
+ public class Login
+ {
+ public String email { get; set; }
+ public String password { get; set; }
+ }
+
+ public class Register
+ {
+ public String name { get; set; }
+ public String username { get; set; }
+ public String email { get; set; }
+ public String password { get; set; }
+ }
+
+ public class JustMail
+ {
+ public String email { get; set; }
+ }
+
+ public class ResetPass
+ {
+ public String email { get; set; }
+ public String newpass { get; set; }
+ public String kod { get; set; }
+ }
+ public class VerifyUser
+ {
+ public String email { get; set; }
+ public String password { get; set; }
+ public String kod { get; set; }
+ }
+}
diff --git a/Backend/Api/Api/Program.cs b/Backend/Api/Api/Program.cs
new file mode 100644
index 0000000..4643937
--- /dev/null
+++ b/Backend/Api/Api/Program.cs
@@ -0,0 +1,78 @@
+using System.Text;
+using Api.Database;
+using Api.Interfaces;
+using Api.Services;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using MongoDB.Driver;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.Configure<DatabaseConnection>(
+ builder.Configuration.GetSection("DatabaseSettings"));
+
+builder.Services.AddSingleton<IDatabaseConnection>(sp =>
+ sp.GetRequiredService<IOptions<DatabaseConnection>>().Value);
+
+builder.Services.AddSingleton<IMongoClient>(s =>
+ new MongoClient(builder.Configuration.GetValue<string>("DatabaseSettings:ConnectionString")));
+
+builder.Services.AddScoped<IUserService, UserService>();
+builder.Services.AddScoped<IJwtService, JwtService>();
+
+builder.Services.AddHttpContextAccessor();
+
+
+
+
+//Add Authentication
+builder.Services.AddAuthentication(
+ JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration.GetSection("AppSettings:JwtToken").Value)),
+ ValidateIssuer = false,
+ ValidateAudience = false
+ };
+
+ });
+
+
+
+builder.Services.AddControllers();
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+builder.Services.AddCors(options =>
+{
+ options.AddPolicy("CorsPolicy",
+ builder =>
+ builder
+ .AllowAnyOrigin()
+ .AllowAnyMethod()
+ .AllowAnyHeader()
+ .AllowCredentials());
+});
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseAuthorization();
+
+//Add Authentication
+app.UseAuthentication();
+
+app.MapControllers();
+
+app.Run();
diff --git a/Backend/Api/Api/Properties/launchSettings.json b/Backend/Api/Api/Properties/launchSettings.json
new file mode 100644
index 0000000..8ad1068
--- /dev/null
+++ b/Backend/Api/Api/Properties/launchSettings.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:61217",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "Api": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5279",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/Backend/Api/Api/Services/JwtService.cs b/Backend/Api/Api/Services/JwtService.cs
new file mode 100644
index 0000000..9d928f7
--- /dev/null
+++ b/Backend/Api/Api/Services/JwtService.cs
@@ -0,0 +1,130 @@
+using System.Data;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using System.Xml.Linq;
+using Api.Interfaces;
+using Api.Models;
+using Microsoft.Extensions.Configuration;
+using Microsoft.IdentityModel.Tokens;
+
+namespace Api.Services
+{
+ public class JwtService : IJwtService
+ {
+ private readonly IConfiguration _config;
+ public JwtService(IConfiguration config)
+ {
+ _config = config;
+ }
+
+ public string GenToken(User user)
+ {
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.ASCII.GetBytes(_config.GetSection("AppSettings:JwtToken").Value);
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Subject = new ClaimsIdentity(new[] { new Claim("id", user._id) }),
+ Expires = DateTime.UtcNow.AddDays(7),
+ SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
+ };
+ var token = tokenHandler.CreateToken(tokenDescriptor);
+ return tokenHandler.WriteToken(token);
+ }
+ public string TokenToId(string token)
+ {
+ if (token == null)
+ return null;
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.ASCII.GetBytes(_config.GetSection("AppSettings:JwtToken").Value);
+ try
+ {
+ tokenHandler.ValidateToken(token, new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(key),
+ ValidateIssuer = false,
+ ValidateAudience = false,
+ }, out SecurityToken validatedToken);
+
+ var jwtToken = (JwtSecurityToken)validatedToken;
+ return jwtToken.Claims.First(x => x.Type == "id").Value;
+ }
+ catch
+ {
+ return null;
+ }
+
+ }
+ public string GenEmailToken(User user)
+ {
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.ASCII.GetBytes(_config.GetSection("AppSettings:EmailToken").Value);
+ Random rand = new Random((int)DateTime.Now.Ticks);
+ int kod = rand.Next(100000, 999999);
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Subject = new ClaimsIdentity(new[] { new Claim("username", user.username), new Claim("kod",kod.ToString()), new Claim("id", user._id) }),
+ Expires = DateTime.UtcNow.AddMinutes(30),
+ SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
+ };
+ var token = tokenHandler.CreateToken(tokenDescriptor);
+ return tokenHandler.WriteToken(token);
+ }
+
+ public string EmailTokenToId(string token)
+ {
+ if (token == null)
+ return null;
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.ASCII.GetBytes(_config.GetSection("AppSettings:EmailToken").Value.ToString());
+ try
+ {
+ tokenHandler.ValidateToken(token, new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(key),
+ ValidateIssuer = false,
+ ValidateAudience = false,
+ ClockSkew = TimeSpan.Zero
+ },
+ out SecurityToken validatedToken);
+ var jwtToken = (JwtSecurityToken)validatedToken;
+ var username = (jwtToken.Claims.First(x => x.Type == "username").Value.ToString());
+ return username;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ public string EmailTokenToKod(string token)
+ {
+ if (token == null)
+ return null;
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.ASCII.GetBytes(_config.GetSection("AppSettings:EmailToken").Value.ToString());
+ try
+ {
+ tokenHandler.ValidateToken(token, new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(key),
+ ValidateIssuer = false,
+ ValidateAudience = false,
+ ClockSkew = TimeSpan.Zero
+ },
+ out SecurityToken validatedToken);
+ var jwtToken = (JwtSecurityToken)validatedToken;
+ var kod = (jwtToken.Claims.First(x => x.Type == "kod").Value.ToString());
+ return kod;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ }
+}
diff --git a/Backend/Api/Api/Services/UserService.cs b/Backend/Api/Api/Services/UserService.cs
new file mode 100644
index 0000000..3002f34
--- /dev/null
+++ b/Backend/Api/Api/Services/UserService.cs
@@ -0,0 +1,271 @@
+using Api.Interfaces;
+using Api.Models;
+using MongoDB.Driver;
+using Microsoft.AspNetCore.Http;
+using System.Security.Claims;
+using MimeKit;
+using MailKit.Net.Smtp;
+
+namespace Api.Services
+{
+ public class UserService : IUserService
+ {
+ private readonly IHttpContextAccessor _httpContext;
+ private readonly IMongoCollection<User> _users;
+ private readonly IJwtService _jwtService;
+ private IConfiguration _configuration;
+ public UserService(IDatabaseConnection settings, IMongoClient mongoClient, IJwtService jwtService, IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
+ {
+ var database = mongoClient.GetDatabase(settings.DatabaseName);
+ _users = database.GetCollection<User>(settings.UserCollectionName);
+ _jwtService = jwtService;
+ this._httpContext = httpContextAccessor;
+ this._configuration = configuration;
+ }
+
+ public async Task<int> createUser(User user)
+ {
+ if (await _users.Find(x => x.email == user.email).FirstOrDefaultAsync() != null)
+ return -1; //email already exists
+ if (await _users.Find(x => x.username == user.username).FirstOrDefaultAsync() != null)
+ return -2; //username already
+ //
+ user.password = hashPassword(user.password);
+ await _users.InsertOneAsync(user);
+ return 1;
+ }
+
+ public async Task<User> deleteUser(string email)
+ {
+ return await _users.FindOneAndDeleteAsync(x => x.email == email);
+ }
+
+ public async Task<User> getUserByEmail(string email)
+ {
+ return await _users.Find(x => x.email == email).SingleOrDefaultAsync();
+ }
+
+ public async Task<User> getUserByUsername(string username)
+ {
+ return await _users.Find(x => x.username == username).SingleOrDefaultAsync();
+ }
+
+ public async Task<List<User>> getUsers()
+ {
+ return await _users.Find(_=>true).ToListAsync();
+ }
+
+ public async Task<User> getUserById(string id)
+ {
+ return await _users.Find(user => user._id == id).SingleOrDefaultAsync();
+
+ }
+
+ public async Task<long> updateUser(User user)
+ {
+ /* vraca broj izmenjenih korisnika
+ * ovako je odradjeno da bi radilo i kada se posalje potpuno novi objekat User-a bez generisanog _id polja
+ */
+ User foundUser = await _users.Find(x => x.email == user.email).SingleOrDefaultAsync();
+ if (foundUser!=null && user._id==null)
+ {
+ user._id = foundUser._id;
+ }
+ ReplaceOneResult res=await _users.ReplaceOneAsync(x => x.email == user.email, user);
+ if(res.IsAcknowledged)
+ return res.ModifiedCount;
+ return 0;
+ }
+
+ private static int difficulty = 10;
+
+ public static String hashPassword(String password)
+ {
+ String salt = BCrypt.Net.BCrypt.GenerateSalt(difficulty);
+ String passwordHash = BCrypt.Net.BCrypt.HashPassword(password, salt);
+
+ return passwordHash;
+ }
+
+ public static Boolean checkPassword(String plainText, String hash)
+ {
+ Boolean verified = false;
+
+ if (hash == null || !hash.StartsWith("$2a$"))
+ return false;
+
+ verified = BCrypt.Net.BCrypt.Verify(plainText, hash);
+
+ return verified;
+
+ }
+
+ public async Task<string> Register(Register register)
+ {
+ if (await _users.FindAsync(x => x.email == register.email && x.verified==true).Result.AnyAsync())
+ return "Email Exists";
+ else if (await _users.FindAsync(x => x.username == register.username && x.verified==true).Result.AnyAsync())
+ return "Username Exists";
+ else
+ {
+ List<User> unverified = await _users.Find(x => (x.username == register.username || x.email == register.email) && x.verified == false).ToListAsync();
+ if (unverified.Count > 0)
+ {
+ foreach(var usr in unverified)
+ {
+ //ako user nema validan emailtoken, a nije verifikovan prethodno, onda se brise iz baze
+ if (_jwtService.EmailTokenToId(usr.emailToken) == null)
+ await _users.FindOneAndDeleteAsync(x => x._id == usr._id);
+ }
+ }
+ }
+
+ var user = new User();
+ user.email = register.email;
+ user.username = register.username;
+ user.name = register.name;
+ user.verified = false;
+ user.password = hashPassword(register.password);
+ user.creationDate = DateTime.Now.ToUniversalTime();
+ user.emailToken = "";
+
+ await _users.InsertOneAsync(user);
+
+ user.emailToken = _jwtService.GenEmailToken(user);
+ await _users.ReplaceOneAsync(x => x._id == user._id, user);
+ SendEmailKod(user);
+
+ return "User Registered";
+ }
+
+ public async Task<Boolean> VerifyUser(VerifyUser login)
+ {
+ User user = await _users.FindAsync(x => x.email == login.email).Result.FirstOrDefaultAsync();
+ if (user != null && checkPassword(login.password, user.password))
+ {
+ var basekod = _jwtService.EmailTokenToKod(user.emailToken);
+ if (basekod != null)
+ if (String.Compare(login.kod,basekod) == 0)
+ {
+ user.verified = true;
+ await _users.ReplaceOneAsync(x => x._id == user._id, user);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public async Task<string> RenewToken(string existingToken)
+ {
+ var id = _jwtService.TokenToId(existingToken);
+ if (id == null)
+ return null;
+ var user = await getUserById(id);
+
+ return _jwtService.GenToken(user);
+ }
+
+ public async Task<string> Login(Login login)
+ {
+ User user = await _users.FindAsync(x => x.email == login.email && x.verified == true).Result.FirstOrDefaultAsync();
+ if (user != null && checkPassword(login.password, user.password))
+ {
+ return _jwtService.GenToken(user);
+ }
+ return null;
+ }
+
+ public async Task<string> UserIdFromJwt()
+ {
+ string id = null;
+ if (_httpContext.HttpContext.User.FindFirstValue("id") != null)
+ {
+ id = _httpContext.HttpContext.User.FindFirstValue("id").ToString();
+ var _id = await _users.FindAsync(x => x._id == id).Result.FirstOrDefaultAsync();
+ if (_id == null)
+ id = null;
+ }
+ return id;
+ }
+
+ public async Task<Boolean> ResendVerifyKod(Login login)
+ {
+ User user = await _users.FindAsync(x => x.email == login.email).Result.FirstOrDefaultAsync();
+ if (user != null && checkPassword(login.password, user.password))
+ {
+ user.emailToken = _jwtService.GenEmailToken(user);
+ await _users.ReplaceOneAsync(x => x._id == user._id, user);
+ SendEmailKod(user);
+
+ return true;
+ }
+ return false;
+ }
+
+ public Boolean SendEmailKod(User user)
+ {
+ MimeMessage message = new MimeMessage();
+ message.From.Add(new MailboxAddress("Tim Oddyssey", _configuration.GetSection("EmailCfg:Email").Value));
+ message.To.Add(MailboxAddress.Parse(user.email));
+ message.Subject = "Vas Oddyssey verifikacioni kod"; //think of something better yeah?
+
+ var kod = _jwtService.EmailTokenToKod(user.emailToken);
+ if (kod == null)
+ return false;
+
+ var bodybuilder = new BodyBuilder();
+ bodybuilder.HtmlBody = String.Format(@"<h3>Verfikacioni kod:</h3><h2>"+kod+"</h2><br><p>Kod traje <b>30</b> minuta</p>");
+ message.Body = bodybuilder.ToMessageBody();
+
+ SmtpClient client = new SmtpClient();
+ try
+ {
+ client.Connect(_configuration.GetSection("EmailCfg:SmtpServer").Value, 465, true);
+ client.Authenticate(_configuration.GetSection("EmailCfg:Email").Value, _configuration.GetSection("EmailCfg:Password").Value);
+ client.Send(message);
+
+ }
+ catch (Exception ex)
+ {
+ return false;
+ }
+ finally
+ {
+ client.Disconnect(true);
+ client.Dispose();
+ }
+ return true;
+ }
+
+ public async Task<Boolean> ForgotPassword(JustMail jm)
+ {
+ User user = await _users.FindAsync(x => x.email == jm.email && x.verified == true).Result.FirstOrDefaultAsync();
+ if (user != null)
+ {
+ user.emailToken = _jwtService.GenEmailToken(user);
+ await _users.ReplaceOneAsync(x => x._id == user._id, user);
+ SendEmailKod(user);
+
+ return true;
+ }
+ return false;
+ }
+
+ public async Task<Boolean> ResetPassword(ResetPass rp)
+ {
+ User user = await _users.FindAsync(x => x.email == rp.email && x.verified == true).Result.FirstOrDefaultAsync();
+ if (user != null)
+ {
+ var basekod = _jwtService.EmailTokenToKod(user.emailToken);
+ if (basekod != null)
+ if (String.Compare(rp.kod, basekod) == 0)
+ {
+ user.password = hashPassword(rp.newpass);
+ await _users.ReplaceOneAsync(x => x._id == user._id, user);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/Backend/Api/Api/appsettings.Development.json b/Backend/Api/Api/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/Backend/Api/Api/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/Backend/Api/Api/appsettings.json b/Backend/Api/Api/appsettings.json
new file mode 100644
index 0000000..48086f3
--- /dev/null
+++ b/Backend/Api/Api/appsettings.json
@@ -0,0 +1,26 @@
+{
+ "AppSettings": {
+ "JwtToken": "PjrVqQJ1P2VOkuWLw7NaZUluT4z7bkau",
+ "EmailToken": "e8X8c0lm9KS7itWi3wgE6BiPXR21WPvO"
+ },
+
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "DatabaseSettings": {
+
+ "ConnectionString": "mongodb://127.0.0.1:27017/",
+ "DatabaseName": "Odyssey",
+ "UserCollectionName": "users"
+
+ },
+ "EmailCfg": {
+ "Email": "oddyssey.brzodolokacije@gmail.com",
+ "SmtpServer": "smtp.gmail.com",
+ "Password": "nrokhfcwahfbqnpp" //msbs#556
+ }
+}