Skip to content

Commit bd5d42d

Browse files
committed
Added the linkedin integration to the project
Still need to identify the user when returned from linkedin
1 parent 7e17c64 commit bd5d42d

11 files changed

Lines changed: 202 additions & 130 deletions

File tree

.gitignore

Lines changed: 8 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,12 @@
1-
# Logs
2-
logs
3-
*.log
1+
/tmp
2+
/out-tsc
3+
4+
/node_modules
45
npm-debug.log*
56
yarn-debug.log*
67
yarn-error.log*
7-
lerna-debug.log*
8-
.pnpm-debug.log*
9-
10-
# Diagnostic reports (https://nodejs.org/api/report.html)
11-
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12-
13-
# Runtime data
14-
pids
15-
*.pid
16-
*.seed
17-
*.pid.lock
18-
19-
# Directory for instrumented libs generated by jscoverage/JSCover
20-
lib-cov
21-
22-
# Coverage directory used by tools like istanbul
23-
coverage
24-
*.lcov
25-
26-
# nyc test coverage
27-
.nyc_output
28-
29-
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30-
.grunt
31-
32-
# Bower dependency directory (https://bower.io/)
33-
bower_components
34-
35-
# node-waf configuration
36-
.lock-wscript
37-
38-
# Compiled binary addons (https://nodejs.org/api/addons.html)
39-
build/Release
40-
41-
# Dependency directories
42-
node_modules/
43-
jspm_packages/
44-
package-lock.json
45-
46-
# Snowpack dependency directory (https://snowpack.dev/)
47-
web_modules/
48-
49-
# TypeScript cache
50-
*.tsbuildinfo
51-
52-
# Optional npm cache directory
53-
.npm
54-
55-
# Optional eslint cache
56-
.eslintcache
57-
58-
# Optional stylelint cache
59-
.stylelintcache
60-
61-
# Microbundle cache
62-
.rpt2_cache/
63-
.rts2_cache_cjs/
64-
.rts2_cache_es/
65-
.rts2_cache_umd/
66-
67-
# Optional REPL history
68-
.node_repl_history
69-
70-
# Output of 'npm pack'
71-
*.tgz
72-
73-
# Yarn Integrity file
74-
.yarn-integrity
75-
76-
# dotenv environment variable files
77-
.env
78-
.env.development.local
79-
.env.test.local
80-
.env.production.local
81-
.env.local
82-
83-
# parcel-bundler cache (https://parceljs.org/)
84-
.cache
85-
.parcel-cache
86-
87-
# Next.js build output
88-
.next
89-
out
90-
91-
# Nuxt.js build / generate output
92-
.nuxt
93-
dist
94-
95-
# Gatsby files
96-
.cache/
97-
# Comment in the public line in if your project uses Gatsby and not Next.js
98-
# https://nextjs.org/blog/next-9-1#public-directory-support
99-
# public
100-
101-
# vuepress build output
102-
.vuepress/dist
103-
104-
# vuepress v2.x temp and cache directory
105-
.temp
106-
.cache
107-
108-
# Docusaurus cache and generated files
109-
.docusaurus
110-
111-
# Serverless directories
112-
.serverless/
113-
114-
# FuseBox cache
115-
.fusebox/
116-
117-
# DynamoDB Local files
118-
.dynamodb/
119-
120-
# TernJS port file
121-
.tern-port
122-
123-
# Stores VSCode versions used for testing VSCode extensions
124-
.vscode-test
8+
/.pnp
9+
.pnp.js
12510

126-
# yarn v2
127-
.yarn/cache
128-
.yarn/unplugged
129-
.yarn/build-state.yml
130-
.yarn/install-state.gz
131-
.pnp.*
132-
/.idea
133-
.DS_Store
11+
.vscode/*
12+
.idea/*

nextstep-backend/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
"axios": "^1.8.3",
2525
"bcrypt": "^5.1.1",
2626
"body-parser": "^1.20.3",
27+
"cookie-parser": "^1.4.7",
2728
"cors": "^2.8.5",
2829
"cross-env": "^7.0.3",
2930
"dotenv": "^16.4.5",
3031
"dotenv-expand": "^12.0.1",
3132
"express": "^4.21.1",
33+
"express-session": "^1.18.1",
3234
"express-unless": "2.1.3",
3335
"express-validator": "^7.2.0",
3436
"firebase-admin": "^13.2.0",
@@ -40,6 +42,9 @@
4042
"mongoose": "^8.8.2",
4143
"multer": "^1.4.5-lts.1",
4244
"office-text-extractor": "^3.0.3",
45+
"passport": "^0.6.0",
46+
"passport-linkedin-oauth2": "github:auth0/passport-linkedin-oauth2#v3.0.0",
47+
"passport-openidconnect": "^0.1.2",
4348
"pdf-lib": "^1.17.1",
4449
"socket.io": "^4.8.1",
4550
"supertest": "^7.0.0",
@@ -51,13 +56,18 @@
5156
"@babel/preset-typescript": "^7.26.0",
5257
"@eslint/js": "^9.17.0",
5358
"@types/bcrypt": "^5.0.2",
59+
"@types/cookie-parser": "^1.4.8",
5460
"@types/cors": "^2.8.17",
5561
"@types/express": "^4.17.1",
62+
"@types/express-session": "^1.18.1",
5663
"@types/express-unless": "2.0.3",
5764
"@types/jest": "^29.5.14",
5865
"@types/js-yaml": "^4.0.9",
5966
"@types/jsonwebtoken": "^9.0.7",
6067
"@types/multer": "^1.4.12",
68+
"@types/passport": "^1.0.9",
69+
"@types/passport-linkedin-oauth2": "^1.5.6",
70+
"@types/passport-openidconnect": "^0.1.3",
6171
"@types/supertest": "^6.0.2",
6272
"@types/swagger-jsdoc": "^6.0.4",
6373
"@types/swagger-ui-express": "^4.1.7",

nextstep-backend/src/app.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ import loadOpenApiFile from "./openapi/openapi_loader";
1616
import resource_routes from './routes/resources_routes';
1717
import resume_routes from './routes/resume_routes';
1818
import githubRoutes from './routes/github_routes';
19+
import linkedinRoutes from './routes/linkedin_routes';
20+
import session from 'express-session';
21+
import passport from 'passport';
22+
import cookieParser from 'cookie-parser';
23+
import('./config/passport');
1924

2025
const specs = swaggerJsdoc(options);
2126

@@ -40,6 +45,22 @@ const removeUndefinedOrEmptyFields = (req: Request, res: Response, next: NextFun
4045
next();
4146
};
4247

48+
49+
app.use(cookieParser());
50+
app.use(session({
51+
secret: 'your-secret',
52+
resave: false,
53+
saveUninitialized: false,
54+
}));
55+
56+
// Initialize Passport
57+
app.use(passport.initialize());
58+
app.use(passport.session());
59+
60+
// Load Passport config
61+
import('./config/passport');
62+
63+
4364
app.use(bodyParser.json());
4465
app.use(removeUndefinedOrEmptyFields);
4566
app.use(bodyParser.urlencoded({ extended: true }));
@@ -60,12 +81,18 @@ app.use(authenticateToken.unless({
6081
{ url: '/comment', methods: ['GET'] },
6182
{ url: '/post', methods: ['GET'] }, // Allow GET to /post
6283
{ url: /^\/resource\/image\/[^\/]+$/, methods: ['GET'] }, // Allow GET to /resource/image/{anything}
84+
{url: '/linkedin/auth'},
85+
{url: '/linkedin/callback'},
6386
]
6487
}));
6588

6689
// Add AUTH middleware for params queries
6790
// To block queries without Authentication
68-
app.use(authenticateTokenForParams);
91+
app.use(authenticateTokenForParams.unless({
92+
path: [
93+
{url: '/linkedin/callback'}
94+
]
95+
}));
6996

7097
app.use('/auth', authRoutes);
7198
app.use('/comment', commentsRoutes);
@@ -76,5 +103,6 @@ app.use('/resource', resource_routes);
76103
app.use('/room', roomsRoutes);
77104
app.use('/resume', resume_routes);
78105
app.use('/github', githubRoutes);
106+
app.use('/linkedin', linkedinRoutes);
79107

80108
export { app, corsOptions };
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import passport from 'passport';
2+
import { Strategy as LinkedInStrategy } from 'passport-linkedin-oauth2';
3+
import dotenv from 'dotenv';
4+
import dotenvExpand from "dotenv-expand";
5+
6+
dotenv.config();
7+
dotenvExpand.expand(dotenv.config());
8+
9+
passport.serializeUser((user: any, done) => {
10+
done(null, user);
11+
});
12+
13+
passport.deserializeUser((user: any, done) => {
14+
done(null, user);
15+
});
16+
17+
passport.use('linkedin', new LinkedInStrategy({
18+
clientID: process.env.LINKEDIN_CLIENT_ID!,
19+
clientSecret: process.env.LINKEDIN_CLIENT_SECRET!,
20+
callbackURL: process.env.LINKEDIN_CALLBACK_URL || 'http://localhost:3000/linkedin/callback',
21+
scope: ['profile', 'openid', 'email'], // Request additional scopes here if needed.
22+
// state: true,
23+
},
24+
(accessToken: string, refreshToken: string, profile: any, done: Function) => {
25+
26+
if (!profile) {
27+
console.error('Failed to fetch user profile');
28+
return done(new Error('No profile returned by LinkedIn'));
29+
}
30+
31+
// Here you could perform further processing (e.g., saving the profile to a database)
32+
console.log('LinkedIn profile:', profile);
33+
console.log('Access Token:', accessToken);
34+
console.log('Refresh Token:', refreshToken);
35+
return done(null, profile);
36+
}
37+
));
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import passport from 'passport';
2+
import {NextFunction, Request, Response} from 'express';
3+
import {CustomRequest} from "types/customRequest";
4+
5+
export const loginFailure = (req: Request, res: Response): void => {
6+
res.status(401).json({ message: 'Authentication Failed' });
7+
};
8+
9+
export const startLinkedIn = (req: CustomRequest, res: Response): void => {
10+
res.cookie('linkedin_temp_user', req.user.id, {
11+
httpOnly: true,
12+
sameSite: 'lax',
13+
secure: false, // TODO - change when https
14+
maxAge: 5 * 60 * 1000
15+
});
16+
17+
res.send({ message: 'Ready for LinkedIn auth' });
18+
};
19+
20+
export const auth = (req: Request, res: Response, next: NextFunction): void => {
21+
const userId = req.cookies.linkedin_temp_user;
22+
if (!userId) {
23+
res.status(401).send('No temp session');
24+
}
25+
else {
26+
const sese = req.session //.tempUserId = userId;
27+
passport.authenticate('linkedin')(req, res, next);
28+
}
29+
}
30+
31+
32+
export const getProfile = (req: Request, res: Response): void => {
33+
if (!req.user) {
34+
res.status(401).json({ message: 'Not authenticated' });
35+
return;
36+
}
37+
38+
const profile = req.user;
39+
const recommendations: Record<string, string> = {};
40+
41+
// if (!profile.displayName) {
42+
// recommendations.name = 'Add your full name to your LinkedIn profile.';
43+
// }
44+
//
45+
// if (!profile.emails || profile.emails.length === 0) {
46+
// recommendations.email = 'Consider adding an email to your LinkedIn profile.';
47+
// }
48+
//
49+
// if (!profile.photos || profile.photos.length === 0) {
50+
// recommendations.profilePhoto = 'Upload a profile picture to enhance your LinkedIn presence.';
51+
// }
52+
53+
res.json({
54+
profile,
55+
recommendations,
56+
});
57+
};

nextstep-backend/src/middleware/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ const authenticateTokenForParams: any & { unless: typeof unless } = async (req:
7171
}
7272
}
7373

74+
authenticateTokenForParams.unless = unless;
75+
76+
7477
const authenticateLogoutToken = async (req: CustomRequest, res: Response, next: NextFunction) => {
7578
authenticateTokenHandler(req, res, next, true)
7679
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {Request, Response, Router} from 'express';
2+
import passport from 'passport';
3+
import {loginFailure, getProfile, startLinkedIn, auth} from '../controllers/linkedin_controller';
4+
import {CustomRequest} from "types/customRequest";
5+
6+
7+
const router = Router();
8+
9+
router.post('/start-linkedin', (req: Request, res: Response) => startLinkedIn(req as CustomRequest, res));
10+
11+
12+
router.get('/auth', (req, res, next) => auth(req, res, next));
13+
14+
router.get('/callback',
15+
passport.authenticate('linkedin', {
16+
failureRedirect: '/failure',
17+
}),
18+
(req, res) => {
19+
const ses = req.session
20+
const userId = "" //req.session.tempUserId;
21+
const { profile, accessToken } = req.user as any;
22+
23+
// TODO: Update your DB with LinkedIn info
24+
console.log('LinkedIn user', profile.displayName);
25+
26+
res.clearCookie('linkedin_temp_user');
27+
res.redirect('/profile');
28+
}
29+
);
30+
31+
router.get('/failure', loginFailure);
32+
router.get('/profile', getProfile);
33+
34+
export default router;

nextstep-frontend/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ dist
1212
dist-ssr
1313
*.local
1414

15+
.env
16+
1517
# Editor directories and files
1618
.vscode/*
1719
!.vscode/extensions.json

0 commit comments

Comments
 (0)