Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix account, settings, and hacker search #881

Open
wants to merge 9 commits into
base: feat/refactor-to-typescript
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
node_modules

# All Environment Files
*.env
.env.*

# Google Cloud Platform Credentials
Expand Down
2 changes: 1 addition & 1 deletion .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -1 +1 @@
npx --no -- commitlint --edit "\${1}"
npx --no -- commitlint --edit
60 changes: 60 additions & 0 deletions src/assets/email/AccountInvitation.mjml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<mjml>
<mj-head>
<mj-title>We've got your application!</mj-title>
<mj-font name="Hind" href="http://fonts.googleapis.com/css?family=Hind:400" />
</mj-head>
<mj-body background-color="#F9F9F9">
<mj-include path="assets/email/Header.mjml" />
<!-- START BODY -->
<mj-section background-color="#FFFFFF" border="5px solid #E9E9E9">
<mj-column>
<mj-spacer />
<mj-text font-family="Hind" line-height="150%" font-size="14px" color="#4D4D4D">
Hey there! 👋
</mj-text>

<mj-text font-family="Hind" line-height="150%" font-size="14px" color="#4D4D4D">
You've been invited to create an account on our <a href="https://app.mchacks.ca/"
style="-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;color:#F2463A;text-decoration:none;">sponsor
dashboard</a>. On the sponsor dashboard you can find important
information about check-in, mentoring, judging, workshops, Discord, and
the schedule for the weekend. On the sponsor dashboard you will be able
to look up hacker applications and information. Additionally, if you
have resume access you will be able to download hacker resumes through
the dashboard.
</mj-text>

<mj-text font-family="Hind" line-height="150%" font-size="14px" color="#4D4D4D">
Sponsor check-in starts at 6:00 pm on Saturday, January 28th on the
McHacks Discord server and opening ceremonies will begin at 7:00 pm. Be
sure to get set up on the McHacks Discord server at <a
href="https://discord.gg/XVSdW9pc"
style="-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;color:#F2463A;text-decoration:none;">https://discord.gg/XVSdW9pc</a>
and contact your coordinator to set up your server roles.
</mj-text>

<mj-text font-family="Hind" line-height="150%" font-size="14px" color="#4D4D4D">
Get access to the sponsor dashboard by clicking on the button below.
</mj-text>

<mj-spacer />
<mj-button href="{{{ link }}}" font-family="Hind" background-color="#F2463A" color="white" border-radius="40px">
Create your account
</mj-button>
<mj-spacer />

<mj-text font-family="Hind" line-height="150%" font-size="14px" color="#4D4D4D">
If you have any questions, feel free to reach out to your coordinator.
</mj-text>

<mj-text font-family="Hind" line-height="150%" font-size="14px" color="#4D4D4D">
McHacks Team
<br />
<a href="https://mchacks.ca" style="color: #F2463A; text-decoration: none;">mchacks.ca</a>
</mj-text>
</mj-column>
</mj-section>
<!-- END BODY -->
<mj-include path="assets/email/Footer.mjml" />
</mj-body>
</mjml>
12 changes: 6 additions & 6 deletions src/constants/authorization-level.constant.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export enum AuthorizationLevel {
Staff = "Staff",
Sponsor = "Sponsor",
Volunteer = "Volunteer",
Hacker = "Hacker",
Account = "Account",
None = "None",
Staff = "Staff",
Sponsor = "Sponsor",
Volunteer = "Volunteer",
Hacker = "Hacker",
Account = "Account",
None = "None"
}
88 changes: 44 additions & 44 deletions src/constants/error.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const SPONSOR_ID_409_MESSAGE = "Conflict with sponsor accountId link";
const VOLUNTEER_ID_409_MESSAGE = "Conflict with volunteer accountId link";
const HACKER_ID_409_MESSAGE = "Conflict with hacker accountId link";
const TEAM_MEMBER_409_MESSAGE =
"Conflict with team member being in another team";
"Conflict with team member being in another team";
const TEAM_NAME_409_MESSAGE = "Conflict with team name already in use";
const HACKER_STATUS_409_MESSAGE = "Conflict with hacker status";
const TEAM_SIZE_409_MESSAGE = "Team full";
Expand All @@ -24,7 +24,7 @@ const VALIDATION_422_MESSAGE = "Validation failed";
const ACCOUNT_DUPLICATE_422_MESSAGE = "Account already exists";
const ROLE_DUPLICATE_422_MESSAGE = "Role already exists";
const SETTINGS_422_MESSAGE =
"openTime must be before closeTime, and closeTime must be before confirmTime";
"openTime must be before closeTime, and closeTime must be before confirmTime";

const ACCOUNT_TOKEN_401_MESSAGE = "Invalid token for account";
const AUTH_401_MESSAGE = "Invalid Authentication";
Expand All @@ -49,46 +49,46 @@ const ROLE_CREATE_500_MESSAGE = "Error while creating role";
const TRAVEL_CREATE_500_MESSAGE = "Error while creating travel";

export {
ACCOUNT_404_MESSAGE,
HACKER_404_MESSAGE,
TEAM_404_MESSAGE,
RESUME_404_MESSAGE,
ACCOUNT_TYPE_409_MESSAGE,
ACCOUNT_EMAIL_409_MESSAGE,
SPONSOR_ID_409_MESSAGE,
VOLUNTEER_ID_409_MESSAGE,
TEAM_MEMBER_409_MESSAGE,
TEAM_MEMBER_422_MESSAGE,
VALIDATION_422_MESSAGE,
ACCOUNT_TOKEN_401_MESSAGE,
AUTH_401_MESSAGE,
AUTH_403_MESSAGE,
ACCOUNT_403_MESSAGE,
TEAM_UPDATE_500_MESSAGE,
HACKER_UPDATE_500_MESSAGE,
HACKER_ID_409_MESSAGE,
ACCOUNT_UPDATE_500_MESSAGE,
HACKER_CREATE_500_MESSAGE,
SPONSOR_404_MESSAGE,
SPONSOR_CREATE_500_MESSAGE,
TEAM_CREATE_500_MESSAGE,
VOLUNTEER_CREATE_500_MESSAGE,
EMAIL_500_MESSAGE,
GENERIC_500_MESSAGE,
ACCOUNT_DUPLICATE_422_MESSAGE,
LOGIN_500_MESSAGE,
HACKER_STATUS_409_MESSAGE,
TEAM_SIZE_409_MESSAGE,
ROLE_DUPLICATE_422_MESSAGE,
SETTINGS_422_MESSAGE,
ROLE_CREATE_500_MESSAGE,
TEAM_NAME_409_MESSAGE,
TEAM_JOIN_SAME_409_MESSAGE,
TEAM_READ_500_MESSAGE,
VOLUNTEER_404_MESSAGE,
SPONSOR_UPDATE_500_MESSAGE,
SETTINGS_404_MESSAGE,
SETTINGS_403_MESSAGE,
TRAVEL_404_MESSAGE,
TRAVEL_CREATE_500_MESSAGE,
ACCOUNT_404_MESSAGE,
HACKER_404_MESSAGE,
TEAM_404_MESSAGE,
RESUME_404_MESSAGE,
ACCOUNT_TYPE_409_MESSAGE,
ACCOUNT_EMAIL_409_MESSAGE,
SPONSOR_ID_409_MESSAGE,
VOLUNTEER_ID_409_MESSAGE,
TEAM_MEMBER_409_MESSAGE,
TEAM_MEMBER_422_MESSAGE,
VALIDATION_422_MESSAGE,
ACCOUNT_TOKEN_401_MESSAGE,
AUTH_401_MESSAGE,
AUTH_403_MESSAGE,
ACCOUNT_403_MESSAGE,
TEAM_UPDATE_500_MESSAGE,
HACKER_UPDATE_500_MESSAGE,
HACKER_ID_409_MESSAGE,
ACCOUNT_UPDATE_500_MESSAGE,
HACKER_CREATE_500_MESSAGE,
SPONSOR_404_MESSAGE,
SPONSOR_CREATE_500_MESSAGE,
TEAM_CREATE_500_MESSAGE,
VOLUNTEER_CREATE_500_MESSAGE,
EMAIL_500_MESSAGE,
GENERIC_500_MESSAGE,
ACCOUNT_DUPLICATE_422_MESSAGE,
LOGIN_500_MESSAGE,
HACKER_STATUS_409_MESSAGE,
TEAM_SIZE_409_MESSAGE,
ROLE_DUPLICATE_422_MESSAGE,
SETTINGS_422_MESSAGE,
ROLE_CREATE_500_MESSAGE,
TEAM_NAME_409_MESSAGE,
TEAM_JOIN_SAME_409_MESSAGE,
TEAM_READ_500_MESSAGE,
VOLUNTEER_404_MESSAGE,
SPONSOR_UPDATE_500_MESSAGE,
SETTINGS_404_MESSAGE,
SETTINGS_403_MESSAGE,
TRAVEL_404_MESSAGE,
TRAVEL_CREATE_500_MESSAGE
};
152 changes: 82 additions & 70 deletions src/controllers/account.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,88 @@ import { join } from "path";
import { Validator } from "@middlewares/validator.middleware";
import AccountConfirmation from "@models/account-confirmation-token.model";
import * as jwt from "jsonwebtoken";
import { InvitationService } from "@app/services/invitation.service";
import Invitation from "@app/models/invitation.model";

@autoInjectable()
@Controller("/account")
export class AccountController {
constructor(
private readonly accountService: AccountService,
private readonly accountConfirmationService: AccountConfirmationService,
private readonly invitationService: InvitationService,
private readonly mailer: EmailService
) {}

@Post("/invite", [
EnsureAuthenticated,
EnsureAuthorization([AuthorizationLevel.Staff])
])
async createWithInvite(
@Request() request: ExpressRequest,
@Response() response: ExpressResponse,
@Body("email") email: string,
@Body("accountType") accountType: string
) {
const inviter = await this.accountService.findByIdentifier(
//@ts-ignore
request.user?.identifier
);

if (inviter) {
const invitation = await this.invitationService.save({
email,
accountType,
inviter
});
await this.mailer.send(
{
to: invitation.email,
subject: "Account Creation Instructions",
html: join(
__dirname,
"../assets/email/AccountInvitation.mjml"
)
},
{
link: this.invitationService.generateLink(
"account/create",
this.invitationService.generateToken(invitation),
accountType
)
},
(error?: any) => {
if (error)
response.status(500).send({
message: ErrorConstants.EMAIL_500_MESSAGE,
data: error
});
}
);
return response.status(200).send({
message: SuccessConstants.ACCOUNT_INVITE,
data: {}
});
} else {
return response.status(404).json({
message: ErrorConstants.ACCOUNT_404_MESSAGE
});
}
}

@Get("/invite", [
EnsureAuthenticated,
EnsureAuthorization([AuthorizationLevel.Staff])
])
async getInvited(@Response() response: ExpressResponse) {
const result = await this.invitationService.find();

response.status(200).json({
message: SuccessConstants.ACCOUNT_READ,
data: result
});
}

@Get("/", [
EnsureAuthenticated,
EnsureAuthorization([
Expand Down Expand Up @@ -120,33 +192,32 @@ export class AccountController {
async create(
@Response() response: ExpressResponse,
@Body() account: Account,
@Headers("X-Invite-Token") token?: string
// @Params("token") token?: string,
// @Params("accountType") accType?: string,
@Headers("token") token?: string
) {
if (token) {
const data = jwt.verify(
token,
process.env.JWT_CONFIRM_ACC_SECRET!
) as {
identifier: number;
const data = jwt.verify(token, process.env.JWT_INVITE_SECRET!) as {
invitation: Invitation;
};

const result = await this.accountConfirmationService.findByIdentifier(
data.identifier
const result = await this.invitationService.findByIdentifier(
data.email
);

if (result) {
account.confirmed = true;
account.accountType = result.accountType;

this.accountConfirmationService.delete(result.identifier);
this.invitationService.delete(data.email);
}
}

const result: Account = await this.accountService.save(account);

if (result) {
const model = await this.accountConfirmationService.save({
accountType: GeneralConstants.HACKER,
accountType: result.accountType,
email: result.email,
confirmationType: GeneralConstants.CONFIRMATION_TYPE_ORGANIC,
account: result
Expand Down Expand Up @@ -202,6 +273,7 @@ export class AccountController {
) {
//TODO - Implement resend e-mail confirmation and verification.
//TODO - A thrifty user can update their password from here and it would not be hashed, we should attempt to block.
delete update.password;
const result = await this.accountService.update(identifier, update);

return result
Expand All @@ -216,64 +288,4 @@ export class AccountController {
}
});
}

@Post("/invite", [
EnsureAuthenticated,
EnsureAuthorization([AuthorizationLevel.Staff])
])
async createWithInvite(
@Response() response: ExpressResponse,
@Body("email") email: string,
@Body("accountType") accountType: string
) {
const model = await this.accountConfirmationService.save({
email: email,
accountType: accountType
});

await this.mailer.send(
{
to: model.email,
subject: "Account Confirmation Instructions",
html: join(
__dirname,
"../assets/email/AccountConfirmation.mjml"
)
},
{
link: this.accountConfirmationService.generateLink(
"confirm",
this.accountConfirmationService.generateToken(
model.identifier,
model.account!.identifier
)
)
},
(error?: any) => {
if (error)
response.status(500).send({
message: ErrorConstants.EMAIL_500_MESSAGE,
data: error
});
}
);

return response.status(200).send({
message: SuccessConstants.ACCOUNT_INVITE,
data: {}
});
}

@Get("/invites", [
EnsureAuthenticated,
EnsureAuthorization([AuthorizationLevel.Staff])
])
async getInvited(@Response() response: ExpressResponse) {
const result: Array<AccountConfirmation> = await this.accountConfirmationService.find();

response.status(200).json({
message: SuccessConstants.ACCOUNT_READ,
data: result
});
}
}
Loading