Compare commits
No commits in common. "51e22a4611dab8edcd7295e4208233e21a49f8ca" and "5c67fb517921060fe3cc0afa887ecd6521ad27ae" have entirely different histories.
51e22a4611
...
5c67fb5179
|
@ -3,14 +3,14 @@ import inProd from "./inProd";
|
|||
import env from "./env-vars";
|
||||
import * as T from "../../../website/src/types/account-types";
|
||||
|
||||
type Address = string | { name: string; address: string };
|
||||
type Address = string | { name: string, address: string };
|
||||
|
||||
const adminAddress: Address = {
|
||||
name: "LingDocs Admin",
|
||||
address: "admin@lingdocs.com",
|
||||
};
|
||||
|
||||
export function getAddress(user: T.LingdocsUser): Address {
|
||||
function getAddress(user: T.LingdocsUser): Address {
|
||||
// TODO: Guard against ""
|
||||
if (!user.name) return user.email || "";
|
||||
return {
|
||||
|
@ -30,7 +30,7 @@ const transporter = nodemailer.createTransport({
|
|||
});
|
||||
|
||||
async function sendEmail(to: Address, subject: string, text: string) {
|
||||
return await transporter.sendMail({
|
||||
await transporter.sendMail({
|
||||
from: adminAddress,
|
||||
to,
|
||||
subject,
|
||||
|
@ -39,34 +39,19 @@ async function sendEmail(to: Address, subject: string, text: string) {
|
|||
}
|
||||
|
||||
// TODO: MAKE THIS A URL ACROSS PROJECT
|
||||
const baseURL = inProd
|
||||
? "https://account.lingdocs.com"
|
||||
: "http://localhost:4000";
|
||||
const baseURL = inProd ? "https://account.lingdocs.com" : "http://localhost:4000";
|
||||
|
||||
export async function sendVerificationEmail({
|
||||
name,
|
||||
uid,
|
||||
email,
|
||||
token,
|
||||
}: {
|
||||
name: string;
|
||||
uid: T.UUID;
|
||||
email: string;
|
||||
token: T.URLToken;
|
||||
}) {
|
||||
export async function sendVerificationEmail(user: T.LingdocsUser, token: T.URLToken) {
|
||||
const subject = "Please Verify Your E-mail";
|
||||
const content = `Hello ${name},
|
||||
const content = `Hello ${user.name},
|
||||
|
||||
Please verify your email by visiting this link: ${baseURL}/email-verification/${uid}/${token}
|
||||
Please verify your email by visiting this link: ${baseURL}/email-verification/${user.userId}/${token}
|
||||
|
||||
LingDocs Admin`;
|
||||
await sendEmail(email, subject, content);
|
||||
await sendEmail(getAddress(user), subject, content);
|
||||
}
|
||||
|
||||
export async function sendPasswordResetEmail(
|
||||
user: T.LingdocsUser,
|
||||
token: T.URLToken
|
||||
) {
|
||||
export async function sendPasswordResetEmail(user: T.LingdocsUser, token: T.URLToken) {
|
||||
const subject = "Reset Your Password";
|
||||
const content = `Hello ${user.name},
|
||||
|
||||
|
@ -90,9 +75,7 @@ LingDocs Admin`;
|
|||
await sendEmail(getAddress(user), subject, content);
|
||||
}
|
||||
|
||||
export async function sendUpgradeRequestToAdmin(
|
||||
userWantingToUpgrade: T.LingdocsUser
|
||||
) {
|
||||
export async function sendUpgradeRequestToAdmin(userWantingToUpgrade: T.LingdocsUser) {
|
||||
const subject = "Account Upgrade Request";
|
||||
const content = `${userWantingToUpgrade.name} - ${userWantingToUpgrade.email} - ${userWantingToUpgrade.userId} is requesting to upgrade to student.`;
|
||||
await sendEmail(adminAddress, subject, content);
|
||||
|
|
|
@ -5,7 +5,10 @@ import {
|
|||
updateLingdocsUser,
|
||||
deleteCouchDbAuthUser,
|
||||
} from "../lib/couch-db";
|
||||
import { getHash, getEmailTokenAndHash } from "../lib/password-utils";
|
||||
import {
|
||||
getHash,
|
||||
getEmailTokenAndHash,
|
||||
} from "../lib/password-utils";
|
||||
import { getTimestamp } from "../lib/time-utils";
|
||||
import {
|
||||
sendVerificationEmail,
|
||||
|
@ -28,43 +31,33 @@ export function canRemoveOneOutsideProvider(user: T.LingdocsUser): boolean {
|
|||
if (user.email && user.password) {
|
||||
return true;
|
||||
}
|
||||
const providersPresent = outsideProviders.filter(
|
||||
(provider) => !!user[provider]
|
||||
);
|
||||
const providersPresent = outsideProviders.filter((provider) => !!user[provider]);
|
||||
return providersPresent.length > 1;
|
||||
}
|
||||
|
||||
export function getVerifiedEmail({
|
||||
emails,
|
||||
}: T.ProviderProfile): string | false {
|
||||
return emails &&
|
||||
emails.length &&
|
||||
export function getVerifiedEmail({ emails }: T.ProviderProfile): string | false {
|
||||
return (
|
||||
emails
|
||||
&& emails.length
|
||||
// @ts-ignore
|
||||
emails[0].verified
|
||||
? emails[0].value
|
||||
: false;
|
||||
&& emails[0].verified
|
||||
) ? emails[0].value : false;
|
||||
}
|
||||
|
||||
export function getEmailFromGoogleProfile(profile: T.GoogleProfile): {
|
||||
email: string | undefined;
|
||||
verified: boolean;
|
||||
} {
|
||||
export function getEmailFromGoogleProfile(profile: T.GoogleProfile): { email: string | undefined, verified: boolean } {
|
||||
if (!profile.emails || profile.emails.length === 0) {
|
||||
return { email: undefined, verified: false };
|
||||
}
|
||||
const em = profile.emails[0];
|
||||
// @ts-ignore // but the verified value *is* there - if not it's still safe
|
||||
const verified = !!em.verified;
|
||||
const verified = !!em.verified
|
||||
return {
|
||||
email: em.value,
|
||||
verified,
|
||||
};
|
||||
}
|
||||
|
||||
export async function upgradeUser(
|
||||
userId: T.UUID,
|
||||
subscription?: T.StripeSubscription
|
||||
): Promise<T.UpgradeUserResponse> {
|
||||
export async function upgradeUser(userId: T.UUID, subscription?: T.StripeSubscription): Promise<T.UpgradeUserResponse> {
|
||||
// add user to couchdb authentication db
|
||||
const { password, userDbName } = await addCouchDbAuthUser(userId);
|
||||
// // create user db
|
||||
|
@ -86,10 +79,7 @@ export async function upgradeUser(
|
|||
};
|
||||
}
|
||||
|
||||
export async function downgradeUser(
|
||||
userId: T.UUID,
|
||||
subscriptionId?: string
|
||||
): Promise<T.DowngradeUserResponse> {
|
||||
export async function downgradeUser(userId: T.UUID, subscriptionId?: string): Promise<T.DowngradeUserResponse> {
|
||||
await deleteCouchDbAuthUser(userId);
|
||||
if (subscriptionId) {
|
||||
stripe.subscriptions.del(subscriptionId);
|
||||
|
@ -118,27 +108,21 @@ export async function denyUserUpgradeRequest(userId: T.UUID): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
export async function createNewUser(
|
||||
input:
|
||||
| {
|
||||
strategy: "local";
|
||||
email: string;
|
||||
name: string;
|
||||
passwordPlainText: string;
|
||||
}
|
||||
| {
|
||||
strategy: "github";
|
||||
profile: T.GitHubProfile;
|
||||
}
|
||||
| {
|
||||
strategy: "google";
|
||||
profile: T.GoogleProfile;
|
||||
}
|
||||
| {
|
||||
strategy: "twitter";
|
||||
profile: T.TwitterProfile;
|
||||
}
|
||||
): Promise<T.LingdocsUser> {
|
||||
export async function createNewUser(input: {
|
||||
strategy: "local",
|
||||
email: string,
|
||||
name: string,
|
||||
passwordPlainText: string,
|
||||
} | {
|
||||
strategy: "github",
|
||||
profile: T.GitHubProfile,
|
||||
} | {
|
||||
strategy: "google",
|
||||
profile: T.GoogleProfile,
|
||||
} | {
|
||||
strategy: "twitter",
|
||||
profile: T.TwitterProfile,
|
||||
}): Promise<T.LingdocsUser> {
|
||||
const userId = getUUID();
|
||||
const now = getTimestamp();
|
||||
if (input.strategy === "local") {
|
||||
|
@ -157,13 +141,8 @@ export async function createNewUser(
|
|||
lastLogin: now,
|
||||
lastActive: now,
|
||||
};
|
||||
await sendVerificationEmail({
|
||||
name: input.name,
|
||||
uid: userId,
|
||||
email: input.email || "",
|
||||
token: email.token,
|
||||
});
|
||||
const user = await insertLingdocsUser(newUser);
|
||||
sendVerificationEmail(user, email.token).catch(console.error);
|
||||
return user;
|
||||
}
|
||||
// GitHub || Twitter
|
||||
|
@ -200,14 +179,9 @@ export async function createNewUser(
|
|||
lastActive: now,
|
||||
accountCreated: now,
|
||||
level: "basic",
|
||||
};
|
||||
}
|
||||
const user = await insertLingdocsUser(newUser);
|
||||
sendVerificationEmail({
|
||||
name: newUser.name,
|
||||
uid: newUser.userId,
|
||||
email: newUser.email || "",
|
||||
token: em.token,
|
||||
}).catch(console.error);
|
||||
sendVerificationEmail(user, em.token);
|
||||
return user;
|
||||
}
|
||||
const newUser: T.LingdocsUser = {
|
||||
|
@ -222,7 +196,7 @@ export async function createNewUser(
|
|||
lastActive: now,
|
||||
accountCreated: now,
|
||||
level: "basic",
|
||||
};
|
||||
}
|
||||
const user = await insertLingdocsUser(newUser);
|
||||
return user;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,9 @@ import {
|
|||
sendUpgradeRequestToAdmin,
|
||||
sendVerificationEmail,
|
||||
} from "../lib/mail-utils";
|
||||
import { upgradeUser } from "../lib/user-utils";
|
||||
import {
|
||||
upgradeUser,
|
||||
} from "../lib/user-utils";
|
||||
import * as T from "../../../website/src/types/account-types";
|
||||
import env from "../lib/env-vars";
|
||||
|
||||
|
@ -55,7 +57,7 @@ apiRouter.put("/user/tests", async (req, res, next) => {
|
|||
message: "posted test results",
|
||||
tests,
|
||||
});
|
||||
} catch (e) {
|
||||
} catch(e) {
|
||||
next(e);
|
||||
}
|
||||
});
|
||||
|
@ -68,46 +70,29 @@ apiRouter.post("/password", async (req, res, next) => {
|
|||
const { oldPassword, password, passwordConfirmed } = req.body;
|
||||
const addingFirstPassword = !req.user.password;
|
||||
if (!oldPassword && !addingFirstPassword) {
|
||||
return sendResponse(res, {
|
||||
ok: false,
|
||||
error: "Please enter your old password",
|
||||
});
|
||||
return sendResponse(res, { ok: false, error: "Please enter your old password" });
|
||||
}
|
||||
if (!password) {
|
||||
return sendResponse(res, {
|
||||
ok: false,
|
||||
error: "Please enter a new password",
|
||||
});
|
||||
return sendResponse(res, { ok: false, error: "Please enter a new password" });
|
||||
}
|
||||
if (!req.user.email) {
|
||||
return sendResponse(res, {
|
||||
ok: false,
|
||||
error: "You need to add an e-mail address first",
|
||||
});
|
||||
return sendResponse(res, { ok: false, error: "You need to add an e-mail address first" });
|
||||
}
|
||||
if (req.user.password) {
|
||||
const matchedOld =
|
||||
(await compareToHash(oldPassword, req.user.password)) ||
|
||||
!req.user.password;
|
||||
const matchedOld = await compareToHash(oldPassword, req.user.password) || !req.user.password;
|
||||
if (!matchedOld) {
|
||||
return sendResponse(res, { ok: false, error: "Incorrect old password" });
|
||||
}
|
||||
}
|
||||
if (password !== passwordConfirmed) {
|
||||
return sendResponse(res, {
|
||||
ok: false,
|
||||
error: "New passwords do not match",
|
||||
});
|
||||
return sendResponse(res, { ok: false, error: "New passwords do not match" });
|
||||
}
|
||||
if (password.length < 6) {
|
||||
return sendResponse(res, { ok: false, error: "New password too short" });
|
||||
return sendResponse(res, {ok: false, error: "New password too short" });
|
||||
}
|
||||
const hash = await getHash(password);
|
||||
await updateLingdocsUser(req.user.userId, { password: hash });
|
||||
sendResponse(res, {
|
||||
ok: true,
|
||||
message: addingFirstPassword ? "Password added" : "Password changed",
|
||||
});
|
||||
sendResponse(res, { ok: true, message: addingFirstPassword ? "Password added" : "Password changed" });
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -117,19 +102,10 @@ apiRouter.put("/email-verification", async (req, res, next) => {
|
|||
try {
|
||||
if (!req.user) throw new Error("user not found");
|
||||
const { token, hash } = await getEmailTokenAndHash();
|
||||
const u = await updateLingdocsUser(req.user.userId, {
|
||||
emailVerified: hash,
|
||||
});
|
||||
sendVerificationEmail({
|
||||
name: u.name,
|
||||
uid: u.userId,
|
||||
email: u.email || "",
|
||||
token,
|
||||
})
|
||||
.then(() => {
|
||||
const u = await updateLingdocsUser(req.user.userId, { emailVerified: hash });
|
||||
sendVerificationEmail(u, token).then(() => {
|
||||
sendResponse(res, { ok: true, message: "e-mail verification sent" });
|
||||
})
|
||||
.catch((err) => {
|
||||
}).catch((err) => {
|
||||
sendResponse(res, { ok: false, error: err });
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -177,9 +153,7 @@ apiRouter.post("/user/upgradeToStudentRequest", async (req, res, next) => {
|
|||
return;
|
||||
}
|
||||
sendUpgradeRequestToAdmin(req.user).catch(console.error);
|
||||
await updateLingdocsUser(req.user.userId, {
|
||||
upgradeToStudentRequest: "waiting",
|
||||
});
|
||||
await updateLingdocsUser(req.user.userId, { upgradeToStudentRequest: "waiting" });
|
||||
res.send({ ok: true, message: "request for upgrade sent" });
|
||||
} catch (e) {
|
||||
next(e);
|
||||
|
@ -197,7 +171,7 @@ apiRouter.delete("/user", async (req, res, next) => {
|
|||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
/**
|
||||
* signs out the user signed in
|
||||
|
|
|
@ -22,7 +22,6 @@ import { upgradeUser, denyUserUpgradeRequest } from "../lib/user-utils";
|
|||
import { validateReCaptcha } from "../lib/recaptcha";
|
||||
import { getTimestamp } from "../lib/time-utils";
|
||||
import {
|
||||
getAddress,
|
||||
sendPasswordResetEmail,
|
||||
sendVerificationEmail,
|
||||
} from "../lib/mail-utils";
|
||||
|
@ -75,13 +74,7 @@ const authRouter = (passport: PassportStatic) => {
|
|||
email,
|
||||
emailVerified: hash,
|
||||
});
|
||||
// TODO: AWAIT THE E-MAIL SEND TO MAKE SURE THE E-MAIL WORKS!
|
||||
sendVerificationEmail({
|
||||
name: updated.name,
|
||||
uid: updated.userId,
|
||||
email: updated.email || "",
|
||||
token,
|
||||
});
|
||||
sendVerificationEmail(updated, token).catch(console.error);
|
||||
return res.render(page, {
|
||||
user: updated,
|
||||
error: null,
|
||||
|
@ -176,9 +169,7 @@ const authRouter = (passport: PassportStatic) => {
|
|||
try {
|
||||
const { email, password, name } = req.body;
|
||||
const existingUser = await getLingdocsUser("email", email);
|
||||
if (existingUser)
|
||||
return res.send({ ok: false, message: "User Already Exists" });
|
||||
try {
|
||||
if (existingUser) return res.send("User Already Exists");
|
||||
const user = await createNewUser({
|
||||
strategy: "local",
|
||||
email,
|
||||
|
@ -189,10 +180,6 @@ const authRouter = (passport: PassportStatic) => {
|
|||
if (err) return next(err);
|
||||
return res.send({ ok: true, user });
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return res.send({ ok: false, message: "Invalid E-mail" });
|
||||
}
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<h1 class="h3 mb-4 fw-normal">Sign in to LingDocs</h1>
|
||||
<!-- <p class="small mb-2">New? Enter an e-mail and password to sign up</p> -->
|
||||
<div class="form-floating mt-3">
|
||||
<input type="email" required class="form-control" id="emailInput" placeholder="name@example.com">
|
||||
<input type="email" pattern='(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])' required class="form-control" id="emailInput" placeholder="name@example.com">
|
||||
<label for="floatingInput">Email address</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
|
@ -98,7 +98,6 @@
|
|||
body: JSON.stringify({ email, password, name, token }),
|
||||
}).then((res) => res.json())
|
||||
.then((res) => {
|
||||
console.log({ res });
|
||||
if (res.ok) {
|
||||
location.reload();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue