diff --git a/src/config/passport.ts b/src/config/passport.ts index c116d243..acea8e30 100644 --- a/src/config/passport.ts +++ b/src/config/passport.ts @@ -2,7 +2,7 @@ import passport from "passport"; import passportLocal from "passport-local"; import passportFacebook from "passport-facebook"; import _ from "lodash"; - +import { Req } from "../interfaces/req"; // import { User, UserType } from '../models/User'; import { User, UserDocument } from "../models/User"; import { Request, Response, NextFunction } from "express"; @@ -10,7 +10,7 @@ import { Request, Response, NextFunction } from "express"; const LocalStrategy = passportLocal.Strategy; const FacebookStrategy = passportFacebook.Strategy; -passport.serializeUser((user, done) => { +passport.serializeUser((user, done) => { done(undefined, user.id); }); @@ -66,7 +66,7 @@ passport.use(new FacebookStrategy({ callbackURL: "/auth/facebook/callback", profileFields: ["name", "email", "link", "locale", "timezone"], passReqToCallback: true -}, (req: any, accessToken, refreshToken, profile, done) => { +}, (req: Req, accessToken, refreshToken, profile, done) => { if (req.user) { User.findOne({ facebook: profile.id }, (err, existingUser) => { if (err) { return done(err); } @@ -74,7 +74,7 @@ passport.use(new FacebookStrategy({ req.flash("errors", { msg: "There is already a Facebook account that belongs to you. Sign in with that account or delete it, then link it with your current account." }); done(err); } else { - User.findById(req.user.id, (err, user: any) => { + User.findById(req.user.id, (err, user: UserDocument) => { if (err) { return done(err); } user.facebook = profile.id; user.tokens.push({ kind: "facebook", accessToken }); @@ -100,7 +100,7 @@ passport.use(new FacebookStrategy({ req.flash("errors", { msg: "There is already an account using this email address. Sign in to that account and link it with Facebook manually from Account Settings." }); done(err); } else { - const user: any = new User(); + const user = new User(); user.email = profile._json.email; user.facebook = profile.id; user.tokens.push({ kind: "facebook", accessToken }); @@ -130,10 +130,10 @@ export const isAuthenticated = (req: Request, res: Response, next: NextFunction) /** * Authorization Required middleware. */ -export const isAuthorized = (req: Request, res: Response, next: NextFunction) => { +export const isAuthorized = (req: Req, res: Response, next: NextFunction) => { const provider = req.path.split("/").slice(-1)[0]; - const user = req.user as UserDocument; + const user = req.user; if (_.find(user.tokens, { kind: provider })) { next(); } else { diff --git a/src/controllers/api.ts b/src/controllers/api.ts index d5816518..b07f3e68 100644 --- a/src/controllers/api.ts +++ b/src/controllers/api.ts @@ -1,8 +1,8 @@ "use strict"; import graph from "fbgraph"; -import { Response, Request, NextFunction } from "express"; -import { UserDocument } from "../models/User"; +import { NextFunction, Request, Response } from "express"; +import { Req } from "../interfaces/req"; /** @@ -19,9 +19,9 @@ export const getApi = (req: Request, res: Response) => { * GET /api/facebook * Facebook API example. */ -export const getFacebook = (req: Request, res: Response, next: NextFunction) => { - const user = req.user as UserDocument; - const token = user.tokens.find((token: any) => token.kind === "facebook"); +export const getFacebook = (req: Req, res: Response, next: NextFunction) => { + const user = req.user; + const token = user.tokens.find((token) => token.kind === "facebook"); graph.setAccessToken(token.accessToken); graph.get(`${user.facebook}?fields=id,name,email,first_name,last_name,gender,link,locale,timezone`, (err: Error, results: graph.FacebookUser) => { if (err) { return next(err); } diff --git a/src/controllers/user.ts b/src/controllers/user.ts index fd1bd813..cf1df7c2 100644 --- a/src/controllers/user.ts +++ b/src/controllers/user.ts @@ -8,6 +8,8 @@ import { IVerifyOptions } from "passport-local"; import { WriteError } from "mongodb"; import { check, sanitize, validationResult } from "express-validator"; import "../config/passport"; +import { isKnownProvider } from "../interfaces/providers"; +import { Req } from "../interfaces/req"; /** * GET /login @@ -130,7 +132,7 @@ export const getAccount = (req: Request, res: Response) => { * POST /account/profile * Update profile information. */ -export const postUpdateProfile = (req: Request, res: Response, next: NextFunction) => { +export const postUpdateProfile = (req: Req, res: Response, next: NextFunction) => { check("email", "Please enter a valid email address.").isEmail(); // eslint-disable-next-line @typescript-eslint/camelcase sanitize("email").normalizeEmail({ gmail_remove_dots: false }); @@ -142,7 +144,7 @@ export const postUpdateProfile = (req: Request, res: Response, next: NextFunctio return res.redirect("/account"); } - const user = req.user as UserDocument; + const user = req.user; User.findById(user.id, (err, user: UserDocument) => { if (err) { return next(err); } user.email = req.body.email || ""; @@ -168,7 +170,7 @@ export const postUpdateProfile = (req: Request, res: Response, next: NextFunctio * POST /account/password * Update current password. */ -export const postUpdatePassword = (req: Request, res: Response, next: NextFunction) => { +export const postUpdatePassword = (req: Req, res: Response, next: NextFunction) => { check("password", "Password must be at least 4 characters long").isLength({ min: 4 }); check("confirmPassword", "Passwords do not match").equals(req.body.password); @@ -179,7 +181,7 @@ export const postUpdatePassword = (req: Request, res: Response, next: NextFuncti return res.redirect("/account"); } - const user = req.user as UserDocument; + const user = req.user; User.findById(user.id, (err, user: UserDocument) => { if (err) { return next(err); } user.password = req.body.password; @@ -195,8 +197,8 @@ export const postUpdatePassword = (req: Request, res: Response, next: NextFuncti * POST /account/delete * Delete user account. */ -export const postDeleteAccount = (req: Request, res: Response, next: NextFunction) => { - const user = req.user as UserDocument; +export const postDeleteAccount = (req: Req, res: Response, next: NextFunction) => { + const user = req.user; User.remove({ _id: user.id }, (err) => { if (err) { return next(err); } req.logout(); @@ -209,10 +211,13 @@ export const postDeleteAccount = (req: Request, res: Response, next: NextFunctio * GET /account/unlink/:provider * Unlink OAuth provider. */ -export const getOauthUnlink = (req: Request, res: Response, next: NextFunction) => { +export const getOauthUnlink = (req: Req, res: Response, next: NextFunction) => { const provider = req.params.provider; - const user = req.user as UserDocument; - User.findById(user.id, (err, user: any) => { + if (!isKnownProvider(provider)) { + return next(`Unrecognized provider '${provider}'`); + } + const user = req.user; + User.findById(user.id, (err, user) => { if (err) { return next(err); } user[provider] = undefined; user.tokens = user.tokens.filter((token: AuthToken) => token.kind !== provider); @@ -267,7 +272,7 @@ export const postReset = (req: Request, res: Response, next: NextFunction) => { User .findOne({ passwordResetToken: req.params.token }) .where("passwordResetExpires").gt(Date.now()) - .exec((err, user: any) => { + .exec((err, user) => { if (err) { return next(err); } if (!user) { req.flash("errors", { msg: "Password reset token is invalid or has expired." }); @@ -346,14 +351,14 @@ export const postForgot = (req: Request, res: Response, next: NextFunction) => { }); }, function setRandomToken(token: AuthToken, done: Function) { - User.findOne({ email: req.body.email }, (err, user: any) => { + User.findOne({ email: req.body.email }, (err, user) => { if (err) { return done(err); } if (!user) { req.flash("errors", { msg: "Account with that email address does not exist." }); return res.redirect("/forgot"); } - user.passwordResetToken = token; - user.passwordResetExpires = Date.now() + 3600000; // 1 hour + user.passwordResetToken = token.toString(); + user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 hour user.save((err: WriteError) => { done(err, token, user); }); diff --git a/src/interfaces/providers.ts b/src/interfaces/providers.ts new file mode 100644 index 00000000..2930730b --- /dev/null +++ b/src/interfaces/providers.ts @@ -0,0 +1,5 @@ +export const knownProviders = ["facebook"] as const; + +export type KnownProvider = typeof knownProviders[number]; + +export const isKnownProvider = (provider: string): provider is KnownProvider => (knownProviders as readonly string[]).includes(provider); diff --git a/src/interfaces/req.ts b/src/interfaces/req.ts new file mode 100644 index 00000000..39fa98fa --- /dev/null +++ b/src/interfaces/req.ts @@ -0,0 +1,6 @@ +import { Request } from "express"; +import { UserDocument } from "../models/User"; + +export interface Req extends Request { + user: UserDocument; +}