Files
talk/src/core/server/services/jwt/index.ts
T
Wyatt JohnsonandKiwi d37333be89 [CORL 133] API Review (#2197)
* refactor: removed unused subscription code

* refactor: removed management api's

* refactor: cleanup of connections

* refactor: refactored comments edge

* refactor: simplified connection resolving

* feat: added story connection edge

* fix: added story index

* feat: added user pagination and user edge

* fix: added filter to comment query

* fix: removed unused resolvers

* fix: creating a comment reply should require auth

* refactor: cleanup of graph files

* feat: removed display name, made username non-unique

* fix: fixed tests

* fix: fixed tests

* fix: added more api docs

* fix: fixed bug with installer

* refactor: fixes and updates

* fix: added linting for graphql, fixed schema

* feat: added docker build tests

* fix: upped output timeout

* fix: fixed stacktraces in production builds

* fix: removed `git add`

- `git add` was causing issues with
    partial staged changs on files

* feat: improved error messaging for auth

* refactor: cleaned up queue names

* fix: merge error
2019-03-12 15:12:21 +01:00

143 lines
3.5 KiB
TypeScript

import { Redis } from "ioredis";
import jwt, { SignOptions } from "jsonwebtoken";
import { Bearer } from "permit";
import uuid from "uuid/v4";
import { Config } from "talk-server/config";
import { AuthenticationError } from "talk-server/errors";
import { User } from "talk-server/models/user";
import { Request } from "talk-server/types/express";
export enum AsymmetricSigningAlgorithm {
RS256 = "RS256",
RS384 = "RS384",
RS512 = "RS512",
ES256 = "ES256",
ES384 = "ES384",
ES512 = "ES512",
}
export enum SymmetricSigningAlgorithm {
HS256 = "HS256",
HS384 = "HS384",
HS512 = "HS512",
}
export type JWTSigningAlgorithm =
| AsymmetricSigningAlgorithm
| SymmetricSigningAlgorithm;
export interface JWTSigningConfig {
secret: Buffer;
algorithm: JWTSigningAlgorithm;
}
export function createAsymmetricSigningConfig(
algorithm: AsymmetricSigningAlgorithm,
secret: string
): JWTSigningConfig {
return {
// Secrets have their newlines encoded with newline literals.
secret: Buffer.from(secret.replace(/\\n/g, "\n"), "utf8"),
algorithm,
};
}
export function createSymmetricSigningConfig(
algorithm: SymmetricSigningAlgorithm,
secret: string
): JWTSigningConfig {
return {
secret: Buffer.from(secret, "utf8"),
algorithm,
};
}
function isSymmetricSigningAlgorithm(
algorithm: string | SymmetricSigningAlgorithm
): algorithm is SymmetricSigningAlgorithm {
return algorithm in SymmetricSigningAlgorithm;
}
function isAsymmetricSigningAlgorithm(
algorithm: string | AsymmetricSigningAlgorithm
): algorithm is AsymmetricSigningAlgorithm {
return algorithm in AsymmetricSigningAlgorithm;
}
/**
* Parses the config and provides the signing config.
*
* @param config the server configuration
*/
export function createJWTSigningConfig(config: Config): JWTSigningConfig {
const secret = config.get("signing_secret");
const algorithm = config.get("signing_algorithm");
if (isSymmetricSigningAlgorithm(algorithm)) {
return createSymmetricSigningConfig(algorithm, secret);
} else if (isAsymmetricSigningAlgorithm(algorithm)) {
return createAsymmetricSigningConfig(algorithm, secret);
}
throw new AuthenticationError(`invalid algorithm=${algorithm} specified`);
}
export type SigningTokenOptions = Pick<
SignOptions,
"jwtid" | "audience" | "issuer" | "expiresIn" | "notBefore"
>;
export const signTokenString = async (
{ algorithm, secret }: JWTSigningConfig,
user: User,
options: SigningTokenOptions
) =>
jwt.sign({}, secret, {
jwtid: uuid(),
// TODO: (wyattjoh) evaluate allowing configuration?
expiresIn: "1 day",
...options,
subject: user.id,
algorithm,
});
export const signPATString = async (
{ algorithm, secret }: JWTSigningConfig,
user: User,
options: SigningTokenOptions
) =>
jwt.sign({ pat: true }, secret, {
...options,
subject: user.id,
algorithm,
});
export function extractJWTFromRequest(req: Request) {
const permit = new Bearer({
basic: "password",
query: "accessToken",
});
return permit.check(req) || null;
}
function generateJTIRevokedKey(jti: string) {
// jtir: JTI Revoked namespace.
return `jtir:${jti}`;
}
export async function revokeJWT(redis: Redis, jti: string, validFor: number) {
await redis.setex(
generateJTIRevokedKey(jti),
Math.ceil(validFor),
Date.now()
);
}
export async function checkJWTRevoked(redis: Redis, jti: string) {
const expiredAtString = await redis.get(generateJTIRevokedKey(jti));
if (expiredAtString) {
throw new AuthenticationError("JWT was revoked");
}
}