Skip to content

Commit

Permalink
wip: new jwt based authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
dyc3 committed Mar 6, 2024
1 parent 1137ce7 commit 75078a5
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 12 deletions.
24 changes: 24 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,27 @@ The pipeline caches metadata in two places: Redis and the database. Redis is use
Video metadata is cached in the `CachedVideos` table. Search results are cached in Redis. The cache keys are the same as the query.

Cached videos are kept for 30 days. Search results are kept for 24 hours. After these periods, the cache is considered stale, and the pipeline will attempt to refresh the cache when the video is next requested. Direct videos are not cached.



# Authentication

Upon page load, the client will immediately send a request to `/api/auth/grant` to obtain a JWT regardless of whether or not the client has one already. This is to ensure that the client has a valid JWT, and to refresh the JWT if it has expired.

```mermaid
sequenceDiagram
participant C as Client
participant S as Server
participant R as Room
C->>+S: GET /api/auth/grant
S->>-C: 200 OK
C->>+S: Connect to websocket with JWT in cookie
S->>S: Verify JWT
alt JWT is valid
S->>S: Set client ID for room on session
S->>-C: 101 Switching Protocols
S->>R: Join Room
else JWT is invalid
S-xC: 403 Forbidden
end
```
16 changes: 15 additions & 1 deletion server/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import nocache from "nocache";
import usermanager from "../usermanager";
import { OttException } from "ott-common/exceptions";
import { requireApiKey } from "../admin";
import jwt from "jsonwebtoken";
import { conf } from "../ott-config";

const router = express.Router();
router.use(nocache());
const log = getLogger("api/auth");

function createSession(): SessionInfo {
export function createSession(): SessionInfo {
return {
isLoggedIn: false,
username: uniqueNamesGenerator(),
Expand Down Expand Up @@ -53,6 +55,8 @@ export async function authTokenMiddleware(
if (req.headers.authorization && req.headers.authorization.startsWith("Bearer")) {
const token: AuthToken = req.headers.authorization.split(" ")[1];
req.token = token;
} else if (req.cookies && req.cookies.token) {
req.token = req.cookies.token;
}

if (!req.token || !(await tokens.validate(req.token))) {
Expand All @@ -78,6 +82,16 @@ export async function authTokenMiddleware(
}
}
next();

// TODO: set cookie with updated token if it has changed

// FIXME: can't set cookie here because it's too late in the request lifecycle
// const token = jwt.sign(req.ottsession, conf.get("session_secret")); // FIXME: no expiration
// res.cookie("token", token, {
// httpOnly: true,
// });

// TODO: also apply session info update to any connected clients associated with this token
}

router.get("/grant", async (req, res) => {
Expand Down
45 changes: 35 additions & 10 deletions server/auth/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,65 @@
import crypto from "crypto";
import { redisClient } from "../redisclient";
import { AuthToken } from "ott-common/models/types";
import jwt from "jsonwebtoken";
import { conf } from "../ott-config";
import { createSession } from ".";
import { getLogger } from "../logger";

const log = getLogger("auth/tokens");

const PREFIX = "auth";
const EXPIRATION_TIME = 14 * 24 * 60 * 60; // 14 days in seconds
const EXPIRATION_TIME = "14d";
const EXPIRATION_TIME_LOGGED_IN = 120 * 24 * 60 * 60 * 2; // 120 days in seconds

export type SessionInfo =
| { isLoggedIn: false; username: string }
| { isLoggedIn: true; user_id: number };

export async function validate(token: AuthToken): Promise<boolean> {
return (await redisClient.exists(`${PREFIX}:${token}`)) > 0;
// return (await redisClient.exists(`${PREFIX}:${token}`)) > 0;
try {
jwt.verify(token, conf.get("session_secret"));
return true;
} catch (err) {
log.error("Failed to validate token", err);
return false;
}
}

/**
* Mint a new crypto-random auth token so it can be assigned session information.
*/
export async function mint(): Promise<AuthToken> {
const buffer = crypto.randomBytes(512);
const token: AuthToken = buffer.toString("base64");
// const buffer = crypto.randomBytes(512);
// const token: AuthToken = buffer.toString("base64");
const token: AuthToken = jwt.sign(createSession(), conf.get("session_secret"), {
expiresIn: EXPIRATION_TIME,
});
return token;
}

export async function getSessionInfo(token: AuthToken): Promise<SessionInfo> {
const text = await redisClient.get(`${PREFIX}:${token}`);
if (!text) {
const decoded = jwt.verify(token, conf.get("session_secret"));
if (!decoded) {
throw new Error(`No session info found`);
}
const info = JSON.parse(text);
return info;
return decoded as SessionInfo;
// const text = await redisClient.get(`${PREFIX}:${token}`);
// if (!text) {
// throw new Error(`No session info found`);
// }
// const info = JSON.parse(text);
// return info;
}

/** @deprecated This will no longer work because auth tokens are JWTs now. */
export async function setSessionInfo(token: AuthToken, session: SessionInfo): Promise<void> {
const expiration = session.isLoggedIn ? EXPIRATION_TIME_LOGGED_IN : EXPIRATION_TIME;
await redisClient.setEx(`${PREFIX}:${token}`, expiration, JSON.stringify(session));
// const expiration = session.isLoggedIn ? EXPIRATION_TIME_LOGGED_IN : EXPIRATION_TIME;
// await redisClient.setEx(`${PREFIX}:${token}`, expiration, JSON.stringify(session));
log.warn(
"setSessionInfo is deprecated and will no longer work because auth tokens are JWTs now."
);
}

export default {
Expand Down
2 changes: 2 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dayjs": "^1.10.4",
"express": "^4.17.1",
"express-session": "^1.17.0",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"m3u8-parser": "^6.2.0",
"nocache": "^3.0.0",
Expand Down Expand Up @@ -60,6 +61,7 @@
"@types/convict": "^6.1.1",
"@types/express": "^4.17.11",
"@types/express-session": "^1.17.3",
"@types/jsonwebtoken": "^9.0.6",
"@types/lodash": "^4.14.170",
"@types/node": "^18.13.0",
"@types/passport": "1.0.12",
Expand Down
84 changes: 83 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3609,6 +3609,13 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==

"@types/jsonwebtoken@^9.0.6":
version "9.0.6"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3"
integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==
dependencies:
"@types/node" "*"

"@types/keygrip@*":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
Expand Down Expand Up @@ -5572,6 +5579,11 @@ buffer-crc32@~0.2.3:
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==

[email protected]:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==

buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
Expand Down Expand Up @@ -7047,6 +7059,13 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"

[email protected]:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"

editorconfig@^0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5"
Expand Down Expand Up @@ -10181,6 +10200,22 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"

jsonwebtoken@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^7.5.4"

jsprim@^1.2.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
Expand Down Expand Up @@ -10211,6 +10246,23 @@ jsprim@^2.0.2:
object.assign "^4.1.4"
object.values "^1.1.6"

jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"

jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"

keycode@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.1.tgz#09c23b2be0611d26117ea2501c2c391a01f39eff"
Expand Down Expand Up @@ -10368,6 +10420,36 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==

lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==

lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==

lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==

lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==

lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==

lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==

lodash.memoize@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
Expand All @@ -10378,7 +10460,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==

lodash.once@^4.1.1:
lodash.once@^4.0.0, lodash.once@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
Expand Down

0 comments on commit 75078a5

Please sign in to comment.