| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- import { Handler } from "express";
- import fs from "fs";
- import path from "path";
- import passport from "passport";
- import JWT from "jsonwebtoken";
- import axios from "axios";
- import { addDays } from "date-fns";
- import { isAdmin } from "../utils";
- import transporter from "../mail/mail";
- import { resetMailText, verifyMailText } from "../mail/text";
- import {
- createUser,
- changePassword,
- generateApiKey,
- getUser,
- verifyUser,
- requestPasswordReset,
- resetPassword
- } from "../db/user";
- /* Read email template */
- const resetEmailTemplatePath = path.join(
- __dirname,
- "../mail/template-reset.html"
- );
- const verifyEmailTemplatePath = path.join(
- __dirname,
- "../mail/template-verify.html"
- );
- const resetEmailTemplate = fs
- .readFileSync(resetEmailTemplatePath, { encoding: "utf-8" })
- .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN);
- const verifyEmailTemplate = fs
- .readFileSync(verifyEmailTemplatePath, { encoding: "utf-8" })
- .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN);
- /* Function to generate JWT */
- const signToken = (user: UserJoined) =>
- JWT.sign(
- {
- iss: "ApiAuth",
- sub: user.email,
- domain: user.domain || "",
- admin: isAdmin(user.email),
- iat: parseInt((new Date().getTime() / 1000).toFixed(0)),
- exp: parseInt((addDays(new Date(), 7).getTime() / 1000).toFixed(0))
- } as Record<string, any>,
- process.env.JWT_SECRET
- );
- /* Passport.js authentication controller */
- const authenticate = (
- type: "jwt" | "local" | "localapikey",
- error: string,
- isStrict = true
- ) =>
- function auth(req, res, next) {
- if (req.user) return next();
- return passport.authenticate(type, (err, user) => {
- if (err) return res.status(400);
- if (!user && isStrict) return res.status(401).json({ error });
- if (user && isStrict && !user.verified) {
- return res.status(400).json({
- error:
- "Your email address is not verified. " +
- "Click on signup to get the verification link again."
- });
- }
- if (user && user.banned) {
- return res
- .status(400)
- .json({ error: "Your are banned from using this website." });
- }
- if (user) {
- req.user = {
- ...user,
- admin: isAdmin(user.email)
- };
- return next();
- }
- return next();
- })(req, res, next);
- };
- export const authLocal = authenticate(
- "local",
- "Login email and/or password are wrong."
- );
- export const authJwt = authenticate("jwt", "Unauthorized.");
- export const authJwtLoose = authenticate("jwt", "Unauthorized.", false);
- export const authApikey = authenticate(
- "localapikey",
- "API key is not correct.",
- false
- );
- /* reCaptcha controller */
- export const recaptcha: Handler = async (req, res, next) => {
- if (process.env.NODE_ENV === "production" && !req.user) {
- const isReCaptchaValid = await axios({
- method: "post",
- url: "https://www.google.com/recaptcha/api/siteverify",
- headers: {
- "Content-type": "application/x-www-form-urlencoded"
- },
- params: {
- secret: process.env.RECAPTCHA_SECRET_KEY,
- response: req.body.reCaptchaToken,
- remoteip: req.realIP
- }
- });
- if (!isReCaptchaValid.data.success) {
- return res
- .status(401)
- .json({ error: "reCAPTCHA is not valid. Try again." });
- }
- }
- return next();
- };
- export const authAdmin: Handler = async (req, res, next) => {
- if (!req.user.admin) {
- return res.status(401).json({ error: "Unauthorized." });
- }
- return next();
- };
- export const signup: Handler = async (req, res) => {
- const { email, password } = req.body;
- if (password.length > 64) {
- return res.status(400).json({ error: "Maximum password length is 64." });
- }
- if (email.length > 255) {
- return res.status(400).json({ error: "Maximum email length is 255." });
- }
- const user = await getUser(email);
- if (user && user.verified) {
- return res.status(403).json({ error: "Email is already in use." });
- }
- const newUser = await createUser(email, password, user);
- const mail = await transporter.sendMail({
- from: process.env.MAIL_FROM || process.env.MAIL_USER,
- to: newUser.email,
- subject: "Verify your account",
- text: verifyMailText.replace(
- /{{verification}}/gim,
- newUser.verification_token
- ),
- html: verifyEmailTemplate.replace(
- /{{verification}}/gim,
- newUser.verification_token
- )
- });
- if (mail.accepted.length) {
- return res
- .status(201)
- .json({ email, message: "Verification email has been sent." });
- }
- return res
- .status(400)
- .json({ error: "Couldn't send verification email. Try again." });
- };
- export const login: Handler = (req, res) => {
- const token = signToken(req.user);
- return res.status(200).json({ token });
- };
- export const renew: Handler = (req, res) => {
- const token = signToken(req.user);
- return res.status(200).json({ token });
- };
- export const verify: Handler = async (req, _res, next) => {
- const user = await verifyUser(req.params.verificationToken);
- if (user) {
- const token = signToken(user);
- req.token = token;
- }
- return next();
- };
- export const changeUserPassword: Handler = async (req, res) => {
- if (req.body.password.length < 8) {
- return res
- .status(400)
- .json({ error: "Password must be at least 8 chars long." });
- }
- if (req.body.password.length > 64) {
- return res.status(400).json({ error: "Maximum password length is 64." });
- }
- const changedUser = await changePassword(req.user.id, req.body.password);
- if (changedUser) {
- return res
- .status(200)
- .json({ message: "Your password has been changed successfully." });
- }
- return res
- .status(400)
- .json({ error: "Couldn't change the password. Try again later" });
- };
- export const generateUserApiKey = async (req, res) => {
- const apikey = await generateApiKey(req.user.id);
- if (apikey) {
- return res.status(201).json({ apikey });
- }
- return res
- .status(400)
- .json({ error: "Sorry, an error occured. Please try again later." });
- };
- export const userSettings: Handler = (req, res) =>
- res.status(200).json({
- apikey: req.user.apikey || "",
- customDomain: req.user.domain || "",
- homepage: req.user.homepage || ""
- });
- export const requestUserPasswordReset: Handler = async (req, res) => {
- const user = await requestPasswordReset(req.body.email);
- if (!user) {
- return res.status(400).json({ error: "Couldn't reset password." });
- }
- const mail = await transporter.sendMail({
- from: process.env.MAIL_USER,
- to: user.email,
- subject: "Reset your password",
- text: resetMailText
- .replace(/{{resetpassword}}/gm, user.reset_password_token)
- .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN),
- html: resetEmailTemplate
- .replace(/{{resetpassword}}/gm, user.reset_password_token)
- .replace(/{{domain}}/gm, process.env.DEFAULT_DOMAIN)
- });
- if (mail.accepted.length) {
- return res.status(200).json({
- email: user.email,
- message: "Reset password email has been sent."
- });
- }
- return res.status(400).json({ error: "Couldn't reset password." });
- };
- export const resetUserPassword: Handler = async (req, _res, next) => {
- const user: UserJoined = await resetPassword(req.params.resetPasswordToken);
- if (user) {
- const token = signToken(user as UserJoined);
- req.token = token;
- }
- return next();
- };
|