Skip to content

Commit

Permalink
Merge pull request #50 from UoaWDCC/feat/integrate-login
Browse files Browse the repository at this point in the history
Feat/integrate login
  • Loading branch information
GodYazza authored Aug 6, 2024
2 parents 3a10294 + f85a4e5 commit 76a76f0
Show file tree
Hide file tree
Showing 15 changed files with 2,841 additions and 897 deletions.
37 changes: 33 additions & 4 deletions api/src/controllers/login.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@
// import {inject} from '@loopback/core';


import {get, HttpErrors, post, requestBody} from '@loopback/rest';
import {get, HttpErrors, param, post, Request, requestBody, RestBindings} from '@loopback/rest';
import {authenticate} from '@loopback/authentication';
import {loginParams, loginResponse} from './controller-types/login.controller.types';
import {inject} from '@loopback/core';
import {inject, service} from '@loopback/core';
import {repository} from '@loopback/repository';
import {AdminRepository, AlumniRepository, MemberRepository, SponsorRepository} from '../repositories';
import {FsaeUser} from '../models';
import {JwtService, PasswordHasherService} from '../services';
import {FsaeRole, FsaeUser} from '../models';
import {FsaeUserService, JwtService, PasswordHasherService} from '../services';
import {UserProfile} from '@loopback/security';
import {authorize} from '@loopback/authorization';

export class LoginController {
constructor(
@repository(AdminRepository) private adminRepository: AdminRepository,
@repository(AlumniRepository) private alumniRepository: AlumniRepository,
@repository(MemberRepository) private memberRepository: MemberRepository,
@repository(SponsorRepository) private sponsorRepository: SponsorRepository,
@service(FsaeUserService) private fsaeUserService: FsaeUserService,
@inject(RestBindings.Http.REQUEST) private req: Request,
@inject('services.jwtservice') private jwtService: JwtService,
@inject('services.passwordhasher') private passwordHasher: PasswordHasherService
) {}
Expand Down Expand Up @@ -147,6 +150,32 @@ export class LoginController {
return this.getUserToken(credentials, userSearchResults);
}

@get('/user/{userEmail}/role')
async getUserRole(
@param.path.string('userEmail') userEmail: string
) : Promise<string | null> {
return this.fsaeUserService.getUserRole(userEmail);
}

@get('/user/whoami')
// @response(200, PING_RESPONSE)
@authenticate('fsae-jwt')
@authorize({
allowedRoles: [FsaeRole.ADMIN, FsaeRole.SPONSOR, FsaeRole.ALUMNI, FsaeRole.ALUMNI],
scopes: ['allow-non-activated'],
})
whoAmI(): object {
throw Error("todo")
// Todo: Requires usage of UserRepo which is on another ticket.
// // Reply with a greeting, the current time, the url, and request headers
// return {
// greeting: 'This endpoint allows non activated accounts. ',
// date: new Date(),
// url: this.req.url,
// headers: Object.assign({}, this.req.headers),
// };
}

async getUserToken(credentials: loginParams, userSearchResults: FsaeUser[]) : Promise<loginResponse> {
// If no user found, invalid credientials
if (userSearchResults.length === 0) {
Expand Down
49 changes: 11 additions & 38 deletions api/src/controllers/register.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import {AdminRepository, AlumniRepository, MemberRepository, SponsorRepository}
import {HttpErrors, post, requestBody} from '@loopback/rest';
import {createFSAEUserDto} from './controller-types/register.controller.types';
import {Admin, FsaeRole} from '../models';
import {inject} from '@loopback/core';
import {PasswordHasherService} from '../services';
import {inject, service} from '@loopback/core';
import {FsaeUserService, PasswordHasherService} from '../services';

export class RegisterController {
constructor(
@repository(AdminRepository) private adminRepository: AdminRepository,
@repository(AlumniRepository) private alumniRepository: AlumniRepository,
@repository(MemberRepository) private memberRepository: MemberRepository,
@repository(SponsorRepository) private sponsorRepository: SponsorRepository,
@service(FsaeUserService) private fsaeUserService: FsaeUserService,
@inject('services.passwordhasher') private passwordHasher: PasswordHasherService
) {}

Expand Down Expand Up @@ -57,15 +58,8 @@ export class RegisterController {
},
}
})createUserDto: createFSAEUserDto): Promise<Admin> {
// Find user Profile
let userSearchResults = await this.adminRepository.find({
where: {
email: createUserDto.email,
},
});

// If no user found, invalid credientials
if (userSearchResults.length > 0) {
// Prevent duplicate user by email
if (await this.fsaeUserService.doesUserExist(createUserDto.email)) {
throw new HttpErrors.Conflict('Email already exists')
}

Expand Down Expand Up @@ -122,15 +116,8 @@ export class RegisterController {
},
}
})createUserDto: createFSAEUserDto): Promise<Admin> {
// Find user Profile
let userSearchResults = await this.memberRepository.find({
where: {
email: createUserDto.email,
},
});

// If no user found, invalid credientials
if (userSearchResults.length > 0) {
// Prevent duplicate user by email
if (await this.fsaeUserService.doesUserExist(createUserDto.email)) {
throw new HttpErrors.Conflict('Email already exists')
}

Expand Down Expand Up @@ -187,15 +174,8 @@ export class RegisterController {
},
}
})createUserDto: createFSAEUserDto): Promise<Admin> {
// Find user Profile
let userSearchResults = await this.sponsorRepository.find({
where: {
email: createUserDto.email,
},
});

// If no user found, invalid credientials
if (userSearchResults.length > 0) {
// Prevent duplicate user by email
if (await this.fsaeUserService.doesUserExist(createUserDto.email)) {
throw new HttpErrors.Conflict('Email already exists')
}

Expand Down Expand Up @@ -252,15 +232,8 @@ export class RegisterController {
},
}
})createUserDto: createFSAEUserDto): Promise<Admin> {
// Find user Profile
let userSearchResults = await this.alumniRepository.find({
where: {
email: createUserDto.email,
},
});

// If no user found, invalid credientials
if (userSearchResults.length > 0) {
// Prevent duplicate user by email
if (await this.fsaeUserService.doesUserExist(createUserDto.email)) {
throw new HttpErrors.Conflict('Email already exists')
}

Expand Down
41 changes: 41 additions & 0 deletions api/src/services/fsae-user.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {injectable, /* inject, */ BindingScope} from '@loopback/core';
import {repository} from '@loopback/repository';
import {AdminRepository, AlumniRepository, MemberRepository, SponsorRepository} from '../repositories';
import {FsaeRole} from '../models';

@injectable({scope: BindingScope.TRANSIENT})
export class FsaeUserService {
constructor(
@repository(AdminRepository) private adminRepository: AdminRepository,
@repository(AlumniRepository) private alumniRepository: AlumniRepository,
@repository(MemberRepository) private memberRepository: MemberRepository,
@repository(SponsorRepository) private sponsorRepository: SponsorRepository,
) {}

async doesUserExist(email: string): Promise<boolean> {
// This is required as cannot create repo for FsaeUser as its an abstract class
const adminUser = await this.adminRepository.findOne({where: {email}});
const alumniUser = await this.alumniRepository.findOne({where: {email}});
const memberUser = await this.memberRepository.findOne({where: {email}});
const sponsorUser = await this.sponsorRepository.findOne({where: {email}});
return adminUser || alumniUser || memberUser || sponsorUser ? true : false;
}

async getUserRole(email: string) : Promise<FsaeRole | null> {
var user;

user = await this.adminRepository.findOne({where: {email}});
if (user) return FsaeRole.ADMIN;

user = await this.alumniRepository.findOne({where: {email}});
if (user) return FsaeRole.ALUMNI;

user = await this.memberRepository.findOne({where: {email}});
if (user) return FsaeRole.MEMBER;

user = await this.sponsorRepository.findOne({where: {email}});
if (user) return FsaeRole.SPONSOR;

return null;
}
}
1 change: 1 addition & 0 deletions api/src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './jwt.service';
export * from './password-hasher.service';
export * from './fsae-user.service';
5 changes: 0 additions & 5 deletions api/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2252,11 +2252,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==

fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==

function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
Expand Down
41 changes: 35 additions & 6 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@mantine/hooks": "7.8.1",
"@reduxjs/toolkit": "^2.2.5",
"@tabler/icons-react": "^3.7.0",
"axios": "^1.7.2",
"jwt-decode": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
44 changes: 44 additions & 0 deletions web/src/api/ApiInstance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import axios from "axios";

const base_url = "http://localhost:3000/" // Todo: Move to env or config file file

export const apiInstance = axios.create({
baseURL: base_url,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
}
})

// Intercept requests
// Add access token to headers
apiInstance.interceptors.request.use(
config => {
console.log("interceptted add auth token")
const accessToken = localStorage.getItem('accessToken');
console.log(`token is ${accessToken}`)
if (accessToken) {
config.headers['Authorization'] = `Bearer ${accessToken}`;
}
return config;
},
error => {
return Promise.reject(error);
}
);


// Intercept responses
// Handle 401 Unauthorized
apiInstance.interceptors.response.use(
response => {
return response;
},
async error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
// Todo: Can intercept 401 requests
}
return Promise.reject(error);
}
);
Loading

0 comments on commit 76a76f0

Please sign in to comment.