diff --git a/WebApi/Controllers/AccountsController.cs b/WebApi/Controllers/AccountsController.cs index 9d1276d..a74d4bd 100644 --- a/WebApi/Controllers/AccountsController.cs +++ b/WebApi/Controllers/AccountsController.cs @@ -16,7 +16,7 @@ namespace WebApi.Controllers { [Authorize(Roles = "Admin")] - [Route("api/[controller]")] + [Route("api/[controller]"), ApiController] public class AccountsController : ControllerBase { private readonly UserManager _manager; @@ -35,7 +35,7 @@ public AccountsController( // POST: api/accounts/register [HttpPost("register")] - public async Task Register([FromBody]RegisterViewModel model) + public async Task Register(RegisterViewModel model) { var isCardExist = await _service.isCardExist(Guid.Empty, model.CardNo); if (isCardExist) @@ -64,7 +64,8 @@ public async Task Register([FromBody]RegisterViewModel model) Identity = user, FullName = model.FullName, CardNo = model.CardNo, - Position = model.Position + Position = model.Position, + Status = Status.Active }); return new OkObjectResult(syncResult); } diff --git a/WebApi/Controllers/AuthController.cs b/WebApi/Controllers/AuthController.cs index 1a7a86c..2477f76 100644 --- a/WebApi/Controllers/AuthController.cs +++ b/WebApi/Controllers/AuthController.cs @@ -12,7 +12,7 @@ namespace WebApi.Controllers { - [Route("api/[controller]")] + [Route("api/[controller]"), ApiController] public class AuthController : ControllerBase { private readonly UserManager _userManager; @@ -37,7 +37,8 @@ public AuthController( // POST api/auth/login [HttpPost("login")] - public async Task Login([FromBody]LoginViewModel model) + [ValidateAntiForgeryToken] + public async Task Login(LoginViewModel model) { // Check if password is correct var user = await _userManager.FindByNameAsync(model.UserName); @@ -59,24 +60,24 @@ public async Task Login([FromBody]LoginViewModel model) } // POST api/auth/check - [HttpGet("check")] [Authorize] + [HttpGet("check")] public IActionResult Check() { return Ok(); } // POST api/auth/is-admin - [HttpGet("is-admin")] [Authorize(Roles = "Admin")] + [HttpGet("is-admin")] public IActionResult IsAdmin() { return Ok(); } // POST api/auth/is-employee - [HttpGet("is-employee")] [Authorize(Roles = "Employee")] + [HttpGet("is-employee")] public IActionResult IsEmployee() { return Ok(); diff --git a/WebApi/Controllers/ConfigController.cs b/WebApi/Controllers/ConfigController.cs index a11b165..5063413 100644 --- a/WebApi/Controllers/ConfigController.cs +++ b/WebApi/Controllers/ConfigController.cs @@ -16,8 +16,7 @@ namespace WebApi.Controllers { [Authorize(Roles = "Admin")] - [Route("api/[controller]")] - [ApiController] + [Route("api/[controller]"), ApiController] public class ConfigController : ControllerBase { private readonly IConfigService _service; diff --git a/WebApi/Controllers/EmployeeController.cs b/WebApi/Controllers/EmployeeController.cs index 5ff010c..7231e4d 100644 --- a/WebApi/Controllers/EmployeeController.cs +++ b/WebApi/Controllers/EmployeeController.cs @@ -16,8 +16,7 @@ namespace WebApi.Controllers { [Authorize(Roles = "Admin")] - [Route("api/[controller]")] - [ApiController] + [Route("api/[controller]"), ApiController] public class EmployeeController : ControllerBase { private readonly IEmployeeService _service; @@ -46,6 +45,7 @@ public async Task Find(Guid id) // PUT api/employee [HttpPut] + [AllowAnonymous] public async Task Update(EmployeeViewModel model) { // Check if Card No already exist diff --git a/WebApi/Controllers/LogController.cs b/WebApi/Controllers/LogController.cs index ca6b44c..3daf6c7 100644 --- a/WebApi/Controllers/LogController.cs +++ b/WebApi/Controllers/LogController.cs @@ -18,8 +18,7 @@ namespace WebApi.Controllers { [Authorize] - [Route("api/[controller]")] - [ApiController] + [Route("api/[controller]"), ApiController] public class LogController : ControllerBase { private JsonSerializerSettings settings = new JsonSerializerSettings { Formatting = Formatting.Indented, ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; @@ -40,9 +39,10 @@ public async Task Index() } // POST api/log - [HttpPost] [AllowAnonymous] - public async Task Log([FromBody] LogInOutViewModel model) + [ValidateAntiForgeryToken] + [HttpPost] + public async Task Log(LogInOutViewModel model) { // Validate card no. & password var user = await _service.ValidateTimeInOutCredentials(model); @@ -54,9 +54,9 @@ public async Task Log([FromBody] LogInOutViewModel model) } // PUT api/log - [HttpPut] [Authorize(Roles = "Admin")] - public async Task Update([FromBody]LogEditViewModel model) + [HttpPut] + public async Task Update(LogEditViewModel model) { return new OkObjectResult(await _service.UpdateAsync(model)); } diff --git a/WebApi/Controllers/XsrfTokenController.cs b/WebApi/Controllers/XsrfTokenController.cs new file mode 100644 index 0000000..d1d20e2 --- /dev/null +++ b/WebApi/Controllers/XsrfTokenController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Antiforgery; +using Microsoft.AspNetCore.Mvc; + +namespace WebApi.Controllers +{ + [Route("api/[controller]"), ApiController] + public class XsrfTokenController : ControllerBase + { + private readonly IAntiforgery _antiforgery; + + public XsrfTokenController(IAntiforgery antiforgery) + { + _antiforgery = antiforgery; + } + + [HttpGet] + public IActionResult Get() + { + var tokens = _antiforgery.GetAndStoreTokens(HttpContext); + + return new ObjectResult(new { + token = tokens.RequestToken, + tokenName = tokens.HeaderName + }); + } + } +} \ No newline at end of file diff --git a/WebApi/Services/LogService.cs b/WebApi/Services/LogService.cs index a1ff84a..955a214 100644 --- a/WebApi/Services/LogService.cs +++ b/WebApi/Services/LogService.cs @@ -42,7 +42,6 @@ public async Task> GetAllAsync() return await _repoLog.Context.Query() .Where(m => m.Deleted == null) .OrderByDescending(m => m.Created) - .Include(m => m.Employee) .Select(m => new LogViewModel { Id = m.Id, @@ -71,7 +70,6 @@ public async Task FindAsync(Guid id) { return await _repoLog.Context.Query() .Where(m => m.Id == id) - .Include(m => m.Employee) .Select(m => new LogViewModel { Id = m.Id, diff --git a/WebApi/Startup.cs b/WebApi/Startup.cs index abc8ec0..37db5ec 100644 --- a/WebApi/Startup.cs +++ b/WebApi/Startup.cs @@ -29,6 +29,7 @@ using WebApi.Repositories; using WebApi.Helpers; using Hubs.BroadcastHub; +using Microsoft.AspNetCore.Antiforgery; namespace WebApi { @@ -106,28 +107,29 @@ public void ConfigureServices(IServiceCollection services) }); // Add Identity - var builder = services.AddIdentityCore(o => + services.AddIdentityCore(o => { - // configure identity options + // Configure identity options o.Password.RequireDigit = false; o.Password.RequireLowercase = false; o.Password.RequireUppercase = false; o.Password.RequireNonAlphanumeric = false; o.Password.RequiredLength = 6; - }).AddRoles(); - builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services); - builder.AddEntityFrameworkStores().AddDefaultTokenProviders(); + }) + .AddRoles() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); services.AddAutoMapper(); - services.AddMvc(options => - { - // Add automatic model validation - options.Filters.Add(typeof(ValidateModelStateAttribute)); - // options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); - }) - .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); - // services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); + // X-CSRF-Token + services.AddAntiforgery(options=> + { + options.HeaderName = "X-XSRF-Token"; + options.SuppressXFrameOptionsHeader = false; + }); + services.AddCors(); services.AddSignalR(); @@ -192,6 +194,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IService }); }); + // Enable CORS app.UseCors(builder => builder.AllowAnyOrigin() .AllowAnyHeader() @@ -201,9 +204,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IService app.UseAuthentication(); app.UseMvc(); + app.Use(async (context, next) => { - await next(); + await next(); + if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) { context.Request.Path = "/index.html"; @@ -211,15 +216,20 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IService } }); + // Single Page Application set up app.UseDefaultFiles(); app.UseStaticFiles(); + // Set up SignalR Hubs app.UseSignalR(routes => { routes.MapHub("/broadcast"); }); + // Identity user seed CreateUsersAndRoles(services).Wait(); + + // Default Attendance Configuration AttendanceConfiguration(services).Wait(); } diff --git a/WebClient/index.html b/WebClient/index.html index 4d91e1d..7d71c57 100644 --- a/WebClient/index.html +++ b/WebClient/index.html @@ -3,7 +3,7 @@ - BDF Attendance System + Attendance System
diff --git a/WebClient/src/components/change-password.vue b/WebClient/src/components/change-password.vue index 3dddc05..c2e940c 100644 --- a/WebClient/src/components/change-password.vue +++ b/WebClient/src/components/change-password.vue @@ -14,7 +14,7 @@ - Update + Update diff --git a/WebClient/src/components/employee-create.vue b/WebClient/src/components/employee-create.vue index 21ed2e2..fb19edf 100644 --- a/WebClient/src/components/employee-create.vue +++ b/WebClient/src/components/employee-create.vue @@ -32,7 +32,7 @@ Cancel - Save + Save diff --git a/WebClient/src/components/employee-edit.vue b/WebClient/src/components/employee-edit.vue index 83d7b81..fa81c48 100644 --- a/WebClient/src/components/employee-edit.vue +++ b/WebClient/src/components/employee-edit.vue @@ -25,7 +25,7 @@ Cancel - Update + Update diff --git a/WebClient/src/components/employee-list.vue b/WebClient/src/components/employee-list.vue index bfc612b..1baec2f 100644 --- a/WebClient/src/components/employee-list.vue +++ b/WebClient/src/components/employee-list.vue @@ -14,7 +14,7 @@ {{ props.item.position }} {{ props.item.cardNo }} - + edit diff --git a/WebClient/src/components/log-edit.vue b/WebClient/src/components/log-edit.vue index aeea136..4651866 100644 --- a/WebClient/src/components/log-edit.vue +++ b/WebClient/src/components/log-edit.vue @@ -81,7 +81,7 @@ Cancel - Update + Update diff --git a/WebClient/src/components/log-employee-form.vue b/WebClient/src/components/log-employee-form.vue index 2d6b20f..46c0c9d 100644 --- a/WebClient/src/components/log-employee-form.vue +++ b/WebClient/src/components/log-employee-form.vue @@ -11,12 +11,12 @@ v-model="form.cardno" required :rules="[required]" v-on:keyup.enter="$refs.password.focus()"> + v-model="form.password" required :rules="[required]" v-on:keyup.enter.prevent="logEmp()"> - Login + Login @@ -49,11 +49,11 @@ export default { this.$store.dispatch(LOG_EMPLOYEE, JSON.stringify(this.form)).then((res) => { this.resetForm() this.$refs.cardNo.focus() - const notifDuration = 4000 + const notifDuration = 8000 if (res.timeOut === '' ) { - this.$notify({ type: 'success', text: 'Welcome '+ res.fullName+ '!
Time in: ' + res.timeIn, duration: notifDuration }) + this.$notify({ type: 'success', text: `Welcome ${res.fullName}!
Time in: ${res.timeIn}`, duration: notifDuration }) } else { - this.$notify({ type: 'success', text: 'Time out: ' + res.timeIn, duration: notifDuration }) + this.$notify({ type: 'warning', text: `Goodbye ${res.fullName}!
Time out: ${res.timeIn}`, duration: notifDuration }) } }) } diff --git a/WebClient/src/components/log-list.vue b/WebClient/src/components/log-list.vue index 22ef46a..f554093 100644 --- a/WebClient/src/components/log-list.vue +++ b/WebClient/src/components/log-list.vue @@ -81,7 +81,7 @@ export default { if(this.isRole('Employee')) { const userId = this.currentUser.user.empId - this.items = this.logs.filter(m => m.EmployeeId == userId) + this.items = this.logs.filter(m => m.employeeId === userId) this.headers = [ { text: "Time In", value: "timeIn" }, { text: "Time Out", value: "timeOut" } diff --git a/WebClient/src/components/login-form.vue b/WebClient/src/components/login-form.vue index b0cf111..f0260db 100644 --- a/WebClient/src/components/login-form.vue +++ b/WebClient/src/components/login-form.vue @@ -10,12 +10,12 @@ v-model="form.username" required :rules="[required]"> + v-model="form.password" required :rules="[required]" v-on:keyup.enter.prevent="loginUser()"> - Login + Login @@ -37,7 +37,7 @@ export default { }, computed: { - ...mapGetters(["currentUser"]) + ...mapGetters(["currentUser", "isLoading"]) }, methods: { diff --git a/WebClient/src/components/nav-bar.vue b/WebClient/src/components/nav-bar.vue index 75934c2..38c7dd0 100644 --- a/WebClient/src/components/nav-bar.vue +++ b/WebClient/src/components/nav-bar.vue @@ -105,7 +105,7 @@ export default { methods: { logout () { this.$store.dispatch(LOGOUT).then(() => { - this.$router.push({ name: 'home' }) + this.$router.push({ name: 'login' }) }) }, isRole(param) { diff --git a/WebClient/src/router/index.js b/WebClient/src/router/index.js index d3cdd73..762ef7c 100644 --- a/WebClient/src/router/index.js +++ b/WebClient/src/router/index.js @@ -43,6 +43,7 @@ const router = new Router({ // before each transition we check if the route need authentication or roles router.beforeEach((to, from, next) => { + // check if the user needs to be authenticated if (to.matched.some(m => m.meta.auth)) { diff --git a/WebClient/src/services/api-service.js b/WebClient/src/services/api-service.js index 08395b6..7e7359b 100644 --- a/WebClient/src/services/api-service.js +++ b/WebClient/src/services/api-service.js @@ -2,17 +2,23 @@ import Vue from 'vue' import store from '@/store' import axios from 'axios' import JwtService from './jwt-service' +import XsrfService from './xsrf-service' import { BASE_URL } from '@/config.js' import { LOADING_START, LOADING_END } from '@/store/actions-type' const ApiService = { init () { axios.defaults.baseURL = BASE_URL + 'api/' + axios.defaults.withCredentials = true + + // Set up anti forgery token + XsrfService.setToken() // Add a request interceptor axios.interceptors.request.use((config) => { config['headers'] = { 'Authorization': 'Bearer ' + JwtService.getToken(), + 'X-XSRF-TOKEN': XsrfService.getToken(), 'Content-Type': 'application/json' } return config; @@ -21,7 +27,7 @@ const ApiService = { // Add a response interceptor axios.interceptors.response.use((response) => { store.commit(LOADING_END) - // always retorn data in response + // always return data in response return response.data; }, error => { store.commit(LOADING_END) @@ -51,8 +57,6 @@ const ApiService = { }) }) }) - - }, put (resource, payload) { diff --git a/WebClient/src/services/xsrf-service.js b/WebClient/src/services/xsrf-service.js new file mode 100644 index 0000000..28aa11b --- /dev/null +++ b/WebClient/src/services/xsrf-service.js @@ -0,0 +1,14 @@ +const XSRF_TOKEN = 'xsrf_token' +import axios from 'axios' + +export default { + getToken () { + return window.localStorage.getItem(XSRF_TOKEN) + }, + + setToken() { + axios.get("xsrfToken").then(res => { + window.localStorage.setItem(XSRF_TOKEN, res.data.token) + }) + } +} \ No newline at end of file