All files / src/routes/public auth.ts

89.36% Statements 42/47
66.66% Branches 12/18
100% Functions 6/6
89.36% Lines 42/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 16062x 1x 1x   1x 1x   1x 68x     68x 68x 2x                 66x 66x                   66x 66x   66x                 66x         66x             66x               1x 4x   4x 4x 4x 4x   4x                             1x         1x 95x   95x   95x                 95x             95x       95x               1x       1x   1x   1x             1x         1x   1x             1x     1x  
import { AppRouteImplementation } from "@ts-rest/express";
import { compareSaltedHash, saltedHash } from "@cooper/backend/src/lib/hashing";
import { addMinutes } from "date-fns";
import { contract } from "@cooper/ts-rest/src/contract";
import { authenticate } from "@cooper/backend/src/middleware/authenticate";
import guard from "@cooper/backend/src/middleware/guard";
 
export const login: AppRouteImplementation<typeof contract.public.auth.login> = async function ({ body, req, res }) {
  const db = req.app.locals.database;
 
  // Check if valid user
  const existingUser = db.auth.users.getUser(body.username);
  if (!existingUser) {
    return {
      status: 401,
      body: {
        error: "Invalid username or password",
      },
    };
  }
 
  // Check if username and password match
  const passwordMatch = compareSaltedHash(body.password, existingUser.password);
  Iif (!passwordMatch) {
    return {
      status: 401,
      body: {
        error: "Invalid username or password",
      },
    };
  }
 
  // Get session details
  const ip = req.ip ?? "Unknown";
  const userAgent = req.get("user-agent") || "Unknown";
 
  const newSession = db.auth.sessions.createSession(
    existingUser.username,
    ip,
    userAgent,
    new Date(),
    addMinutes(new Date(), 30),
  );
 
  // Unexpected error here - there was an issue with the database layer
  Iif (newSession instanceof Error) {
    throw newSession;
  }
 
  // Set secure httpOnly cookie with new session tied to user
  res.cookie("id", newSession.sessionId, {
    secure: true,
    expires: newSession.expires,
    httpOnly: true,
    sameSite: "strict",
  });
 
  return {
    status: 200,
    body: {
      message: "Logged in successfully",
    },
  };
};
 
const logoutHandler: AppRouteImplementation<typeof contract.public.auth.logout> = async function ({ req, res }) {
  const db = req.app.locals.database;
 
  const sessionId = guard(res.session).sessionId;
  if (sessionId != null) {
    db.auth.sessions.deleteSession(sessionId);
    res.clearCookie("id");
 
    return {
      status: 200,
      body: {
        message: "Logged out successfully",
      },
    };
  }
 
  return {
    status: 401,
    body: {
      error: "Unauthorised",
    },
  };
};
export const logout = {
  middleware: [authenticate],
  handler: logoutHandler,
};
 
export const signup: AppRouteImplementation<typeof contract.public.auth.signup> = async function ({ req, body }) {
  const db = req.app.locals.database;
 
  const existingUser = db.auth.users.getUser(body.username);
 
  Iif (existingUser) {
    return {
      status: 400,
      body: {
        error: "User already exists",
      },
    };
  }
 
  const newUser = db.auth.users.createUser({
    username: body.username,
    firstName: body.firstName,
    lastName: body.lastName,
    password: saltedHash(body.password),
  });
 
  Iif (newUser instanceof Error) {
    throw newUser;
  }
 
  return {
    status: 200,
    body: {
      message: "Signed up successfully",
    },
  };
};
 
const getSessionsHandler: AppRouteImplementation<typeof contract.public.auth.getSessions> = async function ({
  req,
  res,
}) {
  const db = req.app.locals.database;
 
  const sessions = db.auth.sessions.getUserSessions(guard(res.session).username);
 
  return {
    status: 200,
    body: {
      sessions,
    },
  };
};
export const getSessions = {
  middleware: [authenticate],
  handler: getSessionsHandler,
};
 
const validSessionHandler: AppRouteImplementation<typeof contract.public.auth.validSession> = async function () {
  // "authed" middleware validates session beforehand, so always return success
  return {
    status: 200,
    body: {
      message: "Valid session",
    },
  };
};
export const validSession = {
  middleware: [authenticate],
  handler: validSessionHandler,
};