add authentication

This commit is contained in:
Michael Fatemi 2021-04-10 15:07:06 -04:00
parent 1ddba82810
commit 43edcb5826
6 changed files with 282 additions and 1 deletions

130
package-lock.json generated
View File

@ -4,6 +4,68 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@hapi/boom": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.2.tgz",
"integrity": "sha512-uJEJtiNHzKw80JpngDGBCGAmWjBtzxDCz17A9NO2zCi8LLBlb5Frpq4pXwyN+2JQMod4pKz5BALwyneCgDg89Q==",
"requires": {
"@hapi/hoek": "9.x.x"
}
},
"@hapi/bourne": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
"integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg=="
},
"@hapi/hoek": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.1.1.tgz",
"integrity": "sha512-CAEbWH7OIur6jEOzaai83jq3FmKmv4PmX1JYfs9IrYcGEVI/lyL1EXJGCj7eFVJ0bg5QR8LMxBlEtA+xKiLpFw=="
},
"@hapi/topo": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz",
"integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==",
"requires": {
"@hapi/hoek": "^9.0.0"
}
},
"@hapi/wreck": {
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-17.1.0.tgz",
"integrity": "sha512-nx6sFyfqOpJ+EFrHX+XWwJAxs3ju4iHdbB/bwR8yTNZOiYmuhA8eCe7lYPtYmb4j7vyK/SlbaQsmTtUrMvPEBw==",
"requires": {
"@hapi/boom": "9.x.x",
"@hapi/bourne": "2.x.x",
"@hapi/hoek": "9.x.x"
}
},
"@sideway/address": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.1.tgz",
"integrity": "sha512-+I5aaQr3m0OAmMr7RQ3fR9zx55sejEYR2BFJaxL+zT3VM2611X0SHvPWIbAUBZVTn/YzYKbV8gJ2oT/QELknfQ==",
"requires": {
"@hapi/hoek": "^9.0.0"
}
},
"@sideway/formula": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg=="
},
"@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
"@types/axios": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz",
"integrity": "sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=",
"requires": {
"axios": "*"
}
},
"@types/body-parser": { "@types/body-parser": {
"version": "1.19.0", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
@ -76,6 +138,16 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/simple-oauth2": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@types/simple-oauth2/-/simple-oauth2-4.1.0.tgz",
"integrity": "sha512-FkAXBdBDFU6i2Rj+N/Sr5bZO8zrMnFnSvyzWtEUXgj7jdDZ0bcgxJ134ctwZbmNvSUzS4Qvt+NvszSbw8Zq+qg=="
},
"@types/uuid": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ=="
},
"accepts": { "accepts": {
"version": "1.3.7", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@ -90,6 +162,14 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
}, },
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"body-parser": { "body-parser": {
"version": "1.19.0", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
@ -233,6 +313,11 @@
"unpipe": "~1.0.0" "unpipe": "~1.0.0"
} }
}, },
"follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA=="
},
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -273,6 +358,18 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
}, },
"joi": {
"version": "17.4.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz",
"integrity": "sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==",
"requires": {
"@hapi/hoek": "^9.0.0",
"@hapi/topo": "^5.0.0",
"@sideway/address": "^4.1.0",
"@sideway/formula": "^3.0.0",
"@sideway/pinpoint": "^2.0.0"
}
},
"media-typer": { "media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -422,6 +519,32 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
}, },
"simple-oauth2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/simple-oauth2/-/simple-oauth2-4.2.0.tgz",
"integrity": "sha512-AV62tGdq9JfLd/uveKpeNtQl+VVm89a35QKlwGuvisYIjCoz2ZmTGRGuSIGiYr+QUhSKJ5kYN1jq2BBa/ac/GQ==",
"requires": {
"@hapi/hoek": "^9.0.4",
"@hapi/wreck": "^17.0.0",
"debug": "^4.1.1",
"joi": "^17.3.0"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"statuses": { "statuses": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@ -456,10 +579,15 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
}, },
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
} }
} }
} }

View File

@ -19,13 +19,17 @@
}, },
"homepage": "https://github.com/myfatemi04/Carpool-Backend#readme", "homepage": "https://github.com/myfatemi04/Carpool-Backend#readme",
"dependencies": { "dependencies": {
"@types/axios": "^0.14.0",
"@types/cors": "^2.8.10", "@types/cors": "^2.8.10",
"@types/express": "^4.17.11", "@types/express": "^4.17.11",
"@types/node": "^14.14.37", "@types/node": "^14.14.37",
"@types/simple-oauth2": "^4.1.0",
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
"axios": "^0.21.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"simple-oauth2": "^4.2.0",
"typescript": "^4.2.4", "typescript": "^4.2.4",
"uuid": "^8.3.2" "uuid": "^8.3.2"
} }

35
src/auth.ts Normal file
View File

@ -0,0 +1,35 @@
import * as simpleoauth2 from 'simple-oauth2';
import { v4 } from 'uuid';
import { getAccountIDFromIonCode } from './auth_ion';
const sessions: {
// Maps to user ID
[sessionID: string]: string;
} = {};
export function getUserIDFromSessionToken(sessionToken: string): string | null {
if (sessionToken in sessions) {
return sessions[sessionToken];
} else {
return null;
}
}
export async function createSessionFromCodeAndProvider(
code: string,
provider: 'ion'
): Promise<string> {
if (provider === 'ion') {
const accountID = await getAccountIDFromIonCode(code);
return createSession(accountID);
}
}
// Returns the newly-created session ID
export function createSession(userID: string): string {
const id = v4();
sessions[id] = userID;
return id;
}

97
src/auth_ion.ts Normal file
View File

@ -0,0 +1,97 @@
import axios from 'axios';
import { AuthorizationCode } from 'simple-oauth2';
import { getUserByEmail, registerUserFromIonProfile } from './data';
import getRedirectUrl from './getRedirectUrl';
export const authorizationCode = new AuthorizationCode({
client: {
id: process.env.ION_CLIENT_ID,
secret: process.env.ION_CLIENT_SECRET,
},
auth: {
tokenHost: 'https://ion.tjhsst.edu/oauth/',
authorizePath: 'https://ion.tjhsst.edu/oauth/authorize',
tokenPath: 'https://ion.tjhsst.edu/oauth/token',
},
});
export const redirectUrl = getRedirectUrl('ion');
export const authorizationUrl = authorizationCode.authorizeURL({
scope: 'read',
redirect_uri: redirectUrl,
});
export interface IonProfile {
id: string;
ion_username: string; // eg "2022mfatemi"
sex: string; // Capitalized
title: null;
display_name: string;
short_name: string;
// Given name
first_name: string;
// Middle name
middle_name?: string;
// Family name
last_name: string;
nickname?: string;
// Internal TJ email (@tjhsst.edu)
tj_email: string;
// Other external personal emails
emails: string[];
grade: {
number: number;
name: string; // freshman, sophomore, junior, senior
};
graduation_year?: number;
user_type: 'student' | 'teacher' | '';
phones: string[]; // eg "Mobile Phone: XXXXXXXXXX", including the text "Mobile Phone"
websites: string[];
counselor?: {
id: string;
// Url to API endpoint for profile info
url: string;
user_type: 'counselor';
username: string;
full_name: string;
first_name: string;
last_name: string;
};
address: string | null;
/** DO NOT USE */
picture: string; // URL of the profile photo of the user. Seems to give empty photos right now.
is_eighth_admin: boolean;
is_announcements_admin: boolean;
is_teacher: boolean;
is_student: boolean;
// 8th period absences count
absences: number;
}
export async function getIonProfile(code: string): Promise<IonProfile> {
let accessToken = await authorizationCode.getToken({
code,
redirect_uri: redirectUrl,
scope: 'read',
});
const profileUrl =
'https://ion.tjhsst.edu/api/profile?format=json&access_token=' +
accessToken.token.access_token;
const response = await axios.get(profileUrl);
return response.data;
}
export async function getAccountIDFromIonCode(code: string) {
const profile = await getIonProfile(code);
const user = await getUserByEmail(profile.tj_email);
if (user == null) {
return await registerUserFromIonProfile(profile);
} else {
return user.id;
}
}

View File

@ -1,3 +1,5 @@
import { IonProfile } from './auth_ion';
/** /**
* Records users by id * Records users by id
*/ */
@ -64,3 +66,15 @@ export function getPoolByID(poolID: string): Carpool.Pool | undefined {
export function getGroupByID(groupID: string): Carpool.Group | undefined { export function getGroupByID(groupID: string): Carpool.Group | undefined {
return groups[groupID]; return groups[groupID];
} }
export async function getUserByEmail(
email: string
): Promise<Carpool.User | undefined> {
return undefined;
}
export async function registerUserFromIonProfile(
profile: IonProfile
): Promise<string> {
return 'new_user_0';
}

3
src/getRedirectUrl.ts Normal file
View File

@ -0,0 +1,3 @@
export default function getRedirectUrl(provider: string) {
return `http://localhost:3000/auth/${provider}/callback`;
}