Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit f921769

Browse files
authored
Merge branch 'master' into refactor/email-ts
2 parents ac0981d + 693a59a commit f921769

File tree

7 files changed

+148
-101
lines changed

7 files changed

+148
-101
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"@types/mailgun-js": "0.22.10",
6464
"@types/mustache": "4.0.1",
6565
"@types/nodemailer": "6.4.0",
66+
"@types/uuid": "8.0.1",
6667
"@typescript-eslint/eslint-plugin": "3.7.0",
6768
"@typescript-eslint/parser": "3.8.0",
6869
"ava": "3.11.1",
File renamed without changes.

server/auth/register.js

Lines changed: 0 additions & 31 deletions
This file was deleted.

server/auth/register.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { v4 as uuidv4 } from 'uuid'
2+
import { makeUser, User } from '../database/users'
3+
import { getToken, tokenKinds } from './token'
4+
import { responses } from '../responses'
5+
import { ValueOf } from 'type-fest'
6+
7+
export const register = async (
8+
{ division, email, name, ctftimeId }: Pick<User, 'division' | 'email' | 'name' | 'ctftimeId'>
9+
): Promise<[typeof responses.goodRegister, { authToken: string }] | ValueOf<typeof responses>> => {
10+
const userUuid = uuidv4()
11+
try {
12+
await makeUser({
13+
division,
14+
email,
15+
name,
16+
id: userUuid,
17+
ctftimeId,
18+
perms: 0
19+
})
20+
} catch (e) {
21+
if (e instanceof Object) {
22+
const { constraint } = e as { constraint?: string }
23+
if (constraint === 'users_ctftime_id_key') {
24+
return responses.badKnownCtftimeId
25+
}
26+
if (constraint === 'users_email_key') {
27+
return responses.badKnownEmail
28+
}
29+
if (constraint === 'users_name_key') {
30+
return responses.badKnownName
31+
}
32+
}
33+
throw e
34+
}
35+
const authToken = await getToken(tokenKinds.auth, userUuid)
36+
return [responses.goodRegister, { authToken }]
37+
}

server/auth/token.js

Lines changed: 0 additions & 70 deletions
This file was deleted.

server/auth/token.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { promisify } from 'util'
2+
import crypto from 'crypto'
3+
import config from '../config/server'
4+
import { ValueOf } from 'type-fest'
5+
import { User } from '../database/users'
6+
7+
const randomBytes = promisify(crypto.randomBytes)
8+
const tokenKey = Buffer.from(config.tokenKey, 'base64')
9+
10+
export enum tokenKinds {
11+
auth = 0,
12+
team = 1,
13+
verify = 2,
14+
ctftimeAuth = 4
15+
}
16+
17+
export enum VerifyTokenKinds {
18+
update = 'update',
19+
register = 'register'
20+
}
21+
22+
export type AuthTokenData = string
23+
export type TeamTokenData = string
24+
export interface VerifyTokenData {
25+
verifyId: string
26+
kind: VerifyTokenKinds
27+
userId: User['id']
28+
email: User['email']
29+
division: User['division']
30+
}
31+
export type CtftimeAuthTokenData = string
32+
33+
// Internal map of type definitions for typing purposes only -
34+
// this type does not describe a real data-structure
35+
type TokenDataTypes = {
36+
[tokenKinds.auth]: AuthTokenData;
37+
[tokenKinds.team]: TeamTokenData;
38+
[tokenKinds.verify]: VerifyTokenData;
39+
[tokenKinds.ctftimeAuth]: CtftimeAuthTokenData;
40+
}
41+
42+
export type Token = string
43+
44+
interface InternalTokenData<Kind extends tokenKinds> {
45+
k: Kind
46+
t: number
47+
d: TokenDataTypes[Kind]
48+
}
49+
50+
const tokenExpiries: Record<ValueOf<typeof tokenKinds>, number> = {
51+
[tokenKinds.auth]: Infinity,
52+
[tokenKinds.team]: Infinity,
53+
[tokenKinds.verify]: config.loginTimeout,
54+
[tokenKinds.ctftimeAuth]: config.loginTimeout
55+
}
56+
57+
const timeNow = () => Math.floor(Date.now() / 1000)
58+
59+
const encryptToken = async <Kind extends tokenKinds>(content: InternalTokenData<Kind>): Promise<Token> => {
60+
const iv = await randomBytes(12)
61+
const cipher = crypto.createCipheriv('aes-256-gcm', tokenKey, iv)
62+
const cipherText = cipher.update(JSON.stringify(content))
63+
cipher.final()
64+
const tokenContent = Buffer.concat([iv, cipherText, cipher.getAuthTag()])
65+
return tokenContent.toString('base64')
66+
}
67+
68+
const decryptToken = async <Kind extends tokenKinds>(token: Token): Promise<InternalTokenData<Kind> | null> => {
69+
try {
70+
const tokenContent = Buffer.from(token, 'base64')
71+
const iv = tokenContent.slice(0, 12)
72+
const authTag = tokenContent.slice(tokenContent.length - 16)
73+
const cipher = crypto.createDecipheriv('aes-256-gcm', tokenKey, iv)
74+
cipher.setAuthTag(authTag)
75+
const plainText = cipher.update(tokenContent.slice(12, tokenContent.length - 16))
76+
cipher.final()
77+
return JSON.parse(plainText.toString()) as InternalTokenData<Kind>
78+
} catch (e) {
79+
return null
80+
}
81+
}
82+
83+
export const getData = async <Kind extends tokenKinds>(expectedTokenKind: Kind, token: Token): Promise<TokenDataTypes[Kind] | null> => {
84+
const content = await decryptToken<Kind>(token)
85+
if (content === null) {
86+
return null
87+
}
88+
const { k: kind, t: createdAt, d: data } = content
89+
if (kind !== expectedTokenKind) {
90+
return null
91+
}
92+
if (createdAt + tokenExpiries[kind] < timeNow()) {
93+
return null
94+
}
95+
return data
96+
}
97+
98+
export const getToken = async <Kind extends tokenKinds>(tokenKind: Kind, data: TokenDataTypes[Kind]): Promise<Token> => {
99+
const token = await encryptToken({
100+
k: tokenKind,
101+
t: timeNow(),
102+
d: data
103+
})
104+
return token
105+
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,11 @@
13061306
dependencies:
13071307
"@types/node" "*"
13081308

1309+
1310+
version "8.0.1"
1311+
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.1.tgz#42958a1a880640b139eea97a1640e1a3f61016d2"
1312+
integrity sha512-2kE8rEFgJpbBAPw5JghccEevQb0XVU0tewF/8h7wPQTeCtoJ6h8qmBIwuzUVm2MutmzC/cpCkwxudixoNYDp1A==
1313+
13091314
"@typescript-eslint/[email protected]":
13101315
version "3.7.0"
13111316
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.7.0.tgz#0f91aa3c83d019591719e597fbdb73a59595a263"

0 commit comments

Comments
 (0)