Skip to content

Commit

Permalink
Merge branch 'bugfix/issue-31_mentee-status' of github.com-dsmabulage…
Browse files Browse the repository at this point in the history
…:dsmabulage/scholarx-backend into bugfix/issue-31_mentee-status
  • Loading branch information
dileepainivossl committed Jul 16, 2024
2 parents a66e08b + e2cb8a4 commit 451ba9c
Show file tree
Hide file tree
Showing 17 changed files with 579 additions and 43 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ CLIENT_URL=http://localhost:5173
IMG_HOST=http://localhost:${SERVER_PORT}
SMTP_MAIL=your_smtp_mail
SMTP_PASSWORD=your_smtp_password
LINKEDIN_CLIENT_ID=your_linkedin_client_id
LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret
LINKEDIN_REDIRECT_URL=http://localhost:${SERVER_PORT}/api/auth/linkedin/callback
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,26 @@ process.env.GOOGLE_CLIENT_SECRET = 'your-client-secret';
process.env.GOOGLE_REDIRECT_URL = 'your-redirect-uri';


We appreciate your interest in ScholarX. Happy contributing! If you have any questions or need assistance, please don't hesitate to reach out to us.
We appreciate your interest in ScholarX. Happy contributing! If you have any questions or need assistance, please don't hesitate to reach out to us.

## Setting up LinkedIn Authentication

1. Create LinkedIn page with the mandatory information.

2. Navigate to https://developer.linkedin.com/

3. Select "Create App":
- Add App name.
- Search for the LinkedIn page that was previously created.
- Upload an image as a Logo.
- Create the App.

4. In Products section select `Share on LinkedIn` and `Sign In with LinkedIn using OpenID Connect` request access.

5. In Auth section edit the `Authorized redirect URLs for your app` and add the redirect url. `http://localhost:3000/api/auth/linkedin/callback`

6. Copy Client Id and Client Secret from the Auth Section.

6. In setting section verify the LinkedIn Page and generate URL.

7. Verify it from your account.
38 changes: 38 additions & 0 deletions package-lock.json

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

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
"debug": "nodemon --watch 'src/**/*.ts' --exec ts-node-dev --inspect --respawn src/server.ts",
"typeorm:db": "npm run build && npx typeorm -d dist/src/configs/dbConfig.js",
"migration:generate": "npm run typeorm:db -- migration:generate",
"migration:run": "npm run typeorm:db -- migration:run"
"migration:run": "npm run typeorm:db -- migration:run",
"seed": "npm run build && node dist/src/scripts/seed-db.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@tsed/passport": "^7.57.1",
"bcrypt": "^5.1.0",
"body-parser": "^1.20.2",
Expand All @@ -33,6 +35,7 @@
"passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"passport-linkedin-oauth2": "github:auth0/passport-linkedin-oauth2#v3.0.0",
"pg": "^8.10.0",
"reflect-metadata": "^0.1.13",
"ts-node": "^10.9.1",
Expand All @@ -53,6 +56,7 @@
"@types/passport": "^1.0.12",
"@types/passport-google-oauth20": "^2.0.14",
"@types/passport-jwt": "^3.0.9",
"@types/passport-linkedin-oauth2": "^1.5.6",
"@types/pg": "^8.10.1",
"@types/prettier": "^2.7.2",
"@types/supertest": "^2.0.12",
Expand Down
23 changes: 12 additions & 11 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import express from 'express'
import type { Express } from 'express'
import bodyParser from 'body-parser'
import cookieParser from 'cookie-parser'
import cors from 'cors'
import type { Express } from 'express'
import express from 'express'
import fs from 'fs'
import passport from 'passport'
import { dataSource } from './configs/dbConfig'
import authRouter from './routes/auth/auth.route'
import profileRouter from './routes/profile/profile.route'
import { CLIENT_URL } from './configs/envConfig'
import './configs/google-passport'
import './configs/linkedin-passport'
import adminRouter from './routes/admin/admin.route'
import mentorRouter from './routes/mentor/mentor.route'
import authRouter from './routes/auth/auth.route'
import categoryRouter from './routes/category/category.route'
import passport from 'passport'
import './configs/passport'
import { CLIENT_URL } from './configs/envConfig'
import cookieParser from 'cookie-parser'
import menteeRouter from './routes/mentee/mentee.route'
import fs from 'fs'
import emailRouter from './routes/emails/emails.route'
import menteeRouter from './routes/mentee/mentee.route'
import mentorRouter from './routes/mentor/mentor.route'
import profileRouter from './routes/profile/profile.route'

const app = express()
const staticFolder = 'uploads'
Expand Down
3 changes: 3 additions & 0 deletions src/configs/envConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ export const CLIENT_URL = process.env.CLIENT_URL ?? ''
export const IMG_HOST = process.env.IMG_HOST ?? ''
export const SMTP_MAIL = process.env.SMTP_MAIL ?? ''
export const SMTP_PASS = process.env.SMTP_PASS ?? ''
export const LINKEDIN_CLIENT_ID = process.env.LINKEDIN_CLIENT_ID ?? ''
export const LINKEDIN_CLIENT_SECRET = process.env.LINKEDIN_CLIENT_SECRET ?? ''
export const LINKEDIN_REDIRECT_URL = process.env.LINKEDIN_REDIRECT_URL ?? ''
19 changes: 13 additions & 6 deletions src/configs/passport.ts → src/configs/google-passport.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { Request } from 'express'
import passport from 'passport'
import { Strategy as JwtStrategy } from 'passport-jwt'
import { dataSource } from './dbConfig'
import Profile from '../entities/profile.entity'
import { dataSource } from './dbConfig'
import {
JWT_SECRET,
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
GOOGLE_REDIRECT_URL,
GOOGLE_CLIENT_SECRET
JWT_SECRET
} from './envConfig'
import type { Request } from 'express'

import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
import { findOrCreateUser } from '../services/auth.service'
import { type User } from '../types'
import { type CreateProfile, type User } from '../types'

passport.use(
new GoogleStrategy(
Expand All @@ -31,7 +31,14 @@ passport.use(
done: (err: Error | null, user?: Profile) => void
) {
try {
const user = await findOrCreateUser(profile)
const createProfile: CreateProfile = {
id: profile.id,
primary_email: profile.emails?.[0]?.value ?? '',
first_name: profile.name?.givenName ?? '',
last_name: profile.name?.familyName ?? '',
image_url: profile.photos?.[0]?.value ?? ''
}
const user = await findOrCreateUser(createProfile)
done(null, user)
} catch (err) {
done(err as Error)
Expand Down
101 changes: 101 additions & 0 deletions src/configs/linkedin-passport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { Request } from 'express'
import passport from 'passport'
import { Strategy as JwtStrategy } from 'passport-jwt'
import Profile from '../entities/profile.entity'
import { dataSource } from './dbConfig'
import {
JWT_SECRET,
LINKEDIN_CLIENT_ID,
LINKEDIN_CLIENT_SECRET,
LINKEDIN_REDIRECT_URL
} from './envConfig'

import { Strategy as LinkedInStrategy } from 'passport-linkedin-oauth2'
import { findOrCreateUser } from '../services/auth.service'
import { type CreateProfile, type LinkedInProfile, type User } from '../types'

passport.use(
new LinkedInStrategy(
{
clientID: LINKEDIN_CLIENT_ID,
clientSecret: LINKEDIN_CLIENT_SECRET,
callbackURL: LINKEDIN_REDIRECT_URL,
scope: ['openid', 'email', 'profile'],
passReqToCallback: true
},
async function (
req: Request,
accessToken: string,
refreshToken: string,
profile: passport.Profile,
done: (err: Error | null, user?: Profile) => void
) {
try {
const data = profile as unknown as LinkedInProfile
const createProfile: CreateProfile = {
id: data.id,
primary_email: data?.email ?? '',
first_name: data?.givenName ?? '',
last_name: data?.familyName ?? '',
image_url: data?.picture ?? ''
}
const user = await findOrCreateUser(createProfile)
done(null, user)
} catch (err) {
done(err as Error)
}
}
)
)

passport.serializeUser((user: Express.User, done) => {
done(null, (user as User).primary_email)
})

passport.deserializeUser(async (primary_email: string, done) => {
try {
const profileRepository = dataSource.getRepository(Profile)
const user = await profileRepository.findOne({
where: { primary_email },
relations: ['mentor', 'mentee']
})
done(null, user)
} catch (err) {
done(err)
}
})

const cookieExtractor = (req: Request): string => {
let token = null
if (req?.cookies) {
token = req.cookies.jwt
}
return token
}

const options = {
jwtFromRequest: cookieExtractor,
secretOrKey: JWT_SECRET
}

passport.use(
new JwtStrategy(options, async (jwtPayload, done) => {
try {
const profileRepository = dataSource.getRepository(Profile)
const profile = await profileRepository.findOne({
where: { uuid: jwtPayload.userId },
relations: ['mentor', 'mentee']
})

if (!profile) {
done(null, false)
} else {
done(null, profile)
}
} catch (error) {
done(error, false)
}
})
)

export default passport
25 changes: 23 additions & 2 deletions src/controllers/admin/emailTemplate.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { Request, Response } from 'express'
import type { ApiResponse } from '../../types'
import type EmailTemplate from '../../entities/emailTemplate.entity'
import type Profile from '../../entities/profile.entity'
import { ProfileTypes } from '../../enums'
import {
createEmailTemplate,
deleteEmailTemplateById,
getEmailTemplateById
} from '../../services/admin/emailTemplate.service'
import type EmailTemplate from '../../entities/emailTemplate.entity'
import type { ApiResponse } from '../../types'

export const addEmailTemplate = async (
req: Request,
Expand Down Expand Up @@ -57,3 +58,23 @@ export const getEmailTemplate = async (
return res.status(500).json({ error: err })
}
}

export const deleteEmailTemplate = async (
req: Request,
res: Response
): Promise<ApiResponse<EmailTemplate>> => {
try {
const user = req.user as Profile
const templateId = req.params.templateId

if (user.type !== ProfileTypes.ADMIN) {
return res.status(403).json({ message: 'Only Admins are allowed' })
}

const { statusCode, message } = await deleteEmailTemplateById(templateId)
return res.status(statusCode).json({ message })
} catch (err) {
console.error('Error executing query', err)
return res.status(500).json({ error: err })
}
}
24 changes: 24 additions & 0 deletions src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ export const googleRedirect = async (
)(req, res, next)
}

export const linkedinRedirect = async (
req: Request,
res: Response,
next: NextFunction
): Promise<void> => {
passport.authenticate(
'linkedin',
{ failureRedirect: '/login' },
(err: Error, user: Profile) => {
if (err) {
next(err)
return
}
if (!user) {
res.redirect('/login')
return
}
signAndSetCookie(res, user.uuid)

res.redirect(process.env.CLIENT_URL ?? '/')
}
)(req, res, next)
}

export const register = async (
req: Request,
res: Response
Expand Down
Loading

0 comments on commit 451ba9c

Please sign in to comment.