Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
SamuelPull committed Nov 25, 2024
1 parent 5b28d60 commit c560eee
Show file tree
Hide file tree
Showing 13 changed files with 57 additions and 27 deletions.
9 changes: 5 additions & 4 deletions api/environment-variables.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion api/src/authenticationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as jsonwebtoken from "jsonwebtoken";
import { config, JwtConfig } from "./config";

export const refreshTokenExpirationInHours = config.refreshTokenExpiration;
export const accessTokenExpirationInMinutesWithrefreshToken = config.accessTokenExpiration;
export const accessTokenExpirationInHoursWithrefreshToken = config.accessTokenExpiration;

/**
* Creates a refresh JWT Token
Expand Down
3 changes: 3 additions & 0 deletions api/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ interface ProcessEnvVars {
ACCESS_TOKEN_EXPIRATION: string;
API_REFRESH_TOKENS_TABLE: string;
REFRESH_TOKEN_STORAGE?: string; // "db" || "memory" || undefined
IDLE_TIME: string;
}

interface DatabaseConfig {
Expand Down Expand Up @@ -124,6 +125,7 @@ interface Config {
db: DatabaseConfig;
dbType: string;
sqlDebug: boolean | undefined;
userIdleTime: number;
accessTokenExpiration: number;
refreshTokenExpiration: number;
refreshTokensTable: string | undefined;
Expand Down Expand Up @@ -202,6 +204,7 @@ export const config: Config = {
},
dbType: envVars.DB_TYPE,
sqlDebug: envVars.SQL_DEBUG,
userIdleTime: envVars.IDLE_TIME,
accessTokenExpiration: envVars.ACCESS_TOKEN_EXPIRATION,
refreshTokenExpiration: envVars.REFRESH_TOKEN_EXPIRATION,
refreshTokensTable: envVars.API_REFRESH_TOKENS_TABLE,
Expand Down
5 changes: 3 additions & 2 deletions api/src/envVarsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,10 @@ export const envVarsSchema = Joi.object({
.empty(["", null])
.note("Refresh token expiration in hours"),
ACCESS_TOKEN_EXPIRATION: Joi.number()
.default(15)
.default(0.25)
.allow("")
.empty(["", null])
.note("Access token expiration in minutes"),
.note("Access token expiration in hours"),
API_REFRESH_TOKENS_TABLE: Joi.string()
.empty(["", null])
.default("refresh_token")
Expand All @@ -291,6 +291,7 @@ export const envVarsSchema = Joi.object({
APPLICATIONINSIGHTS_CONNECTION_STRING: Joi.string()
.allow("")
.note("Azure Application Insights Connection String"),
IDLE_TIME: Joi.number().default(8).allow("").empty(["", null]),
})
.unknown()
.required();
4 changes: 4 additions & 0 deletions api/src/httpd/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import fastifyMetricsPlugin from "fastify-metrics";
import { JwtConfig } from "../config";
import { Ctx } from "../lib/ctx";
import logger from "../lib/logger";
import lastActivityTracker from "../plugins/activity";
import * as Result from "../result";
import { ConnToken } from "../service";
import {
Expand Down Expand Up @@ -239,6 +240,9 @@ export const createBasicApp = (

addTokenHandling(server, jwt);
addLogging(server);
server.register(lastActivityTracker, {
excludePaths: ["/api/user.refreshToken", "/api/notification.list"],
});

server.addContentTypeParser("application/gzip", async function (request, payload) {
request.headers["content-length"] = "1024mb";
Expand Down
27 changes: 22 additions & 5 deletions api/src/plugins/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@ import fp from "fastify-plugin";
import { config } from "../config";
import { AuthenticatedRequest } from "../httpd/lib";
import { kvStore } from "../lib/keyValueStore";
import logger from "../lib/logger";

// todo should not run in refreshtoken route
async function activityTrackingPlugin(
fastify: FastifyInstance,
options: FastifyPluginOptions,
): Promise<void> {
fastify.addHook("preHandler", async (request: AuthenticatedRequest, reply) => {
// if user id in request, set last activity
// if userId in request, set timestamp of last activity, except for routes defined in options.excludePaths (for example, user.refreshtoken, notification.list,...)
const excludedPaths = options.excludePaths.map((p) => (p as string).toLowerCase()) || [];
if (excludedPaths.includes(request.routerPath.toLowerCase())) {
return;
}

if (request.user) {
try {
const userId = request.user?.userId;
if (config.refreshTokenStorage === "memory") {
// TODO save, update last activity
kvStore.save(`lastActivity.${userId}`, Date.now(), Date.now() + 1000 * 60 * 10);
kvStore.save(`lastActivity.${userId}`, Date.now(), Date.now() + 1000 * 60 * 60 * 24);
} else if (config.refreshTokenStorage === "db") {
// create or update last access for userId
}
Expand All @@ -29,9 +33,22 @@ async function activityTrackingPlugin(

// background job to check idle users
const checkIdleUsers = async (): Promise<void> => {
logger.error("***** checkIdleUsers running");
try {
if (config.refreshTokenStorage === "memory") {
// go through all stored lastAccesses
const unexpired = kvStore.getAll();
for (const [k, v] of unexpired) {
logger.error(`***** store: ${k} ${v}`);
if (k.includes("lastActivity.")) {
// if lastActivity value is older then defined, invalidate refresh token
if (Date.now() > (v as number) + config.userIdleTime * 60 * 1000) {
const userId = k.split(".")[1];
logger.error(`***** found inactive user ${userId}`);
kvStore.clear(`lastActivity.${userId}`);
}
}
}
// invalidate refreshTokens of users with last activity older than X
} else if (config.refreshTokenStorage === "db") {
// get all last accesses
Expand All @@ -43,7 +60,7 @@ async function activityTrackingPlugin(
};

// run job every X minutes - setInterval
const intervalId = setInterval(checkIdleUsers, 5 * 60 * 1000);
const intervalId = setInterval(checkIdleUsers, 60 * 1000);

// cleanup
fastify.addHook("onClose", () => {
Expand Down
3 changes: 0 additions & 3 deletions api/src/service/user_refresh_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ export async function validateRefreshToken(
let storedRefreshToken: { userId: string; validUntil: number } | undefined;

if (config.refreshTokenStorage === "memory") {
// todo delete next 2 lines
const storeValues = kvStore.getAll();
logger.error(JSON.stringify(storeValues, null, 2));
storedRefreshToken = kvStore.get(`refreshToken.${refreshToken}`) as
| { userId: string; validUntil: number }
| undefined;
Expand Down
9 changes: 5 additions & 4 deletions api/src/user_authenticate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as jsonwebtoken from "jsonwebtoken";
import { VError } from "verror";

import {
accessTokenExpirationInMinutesWithrefreshToken,
accessTokenExpirationInHoursWithrefreshToken,
createRefreshJWTToken,
refreshTokenExpirationInHours,
} from "./authenticationUtils";
Expand Down Expand Up @@ -262,9 +262,10 @@ export function addHttpHandler(

if (config.refreshTokenStorage === "memory") {
kvStore.save(
`refreshToken.${refreshToken}`,
`refreshToken.${token.userId}`,
{
userId: token.userId,
token: refreshToken,
},
refreshTokenExpiration,
);
Expand Down Expand Up @@ -302,7 +303,7 @@ export function addHttpHandler(
};
// conditionally add token expiration to payload
if (config.refreshTokenStorage && ["db", "memory"].includes(config.refreshTokenStorage)) {
body.data.accessTokenExp = 1000 * 60 * accessTokenExpirationInMinutesWithrefreshToken;
body.data.accessTokenExp = 1000 * 60 * 60 * accessTokenExpirationInHoursWithrefreshToken;
}

reply
Expand Down Expand Up @@ -353,7 +354,7 @@ function createJWT(
const secretOrPrivateKey = algorithm === "RS256" ? Buffer.from(key, "base64") : key;
const expiresIn =
config.refreshTokenStorage && ["db", "memory"].includes(config.refreshTokenStorage)
? `${accessTokenExpirationInMinutesWithrefreshToken}m`
? `${accessTokenExpirationInHoursWithrefreshToken}h`
: "8h";
return jsonwebtoken.sign(
{
Expand Down
7 changes: 4 additions & 3 deletions api/src/user_authenticateAd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as jsonwebtoken from "jsonwebtoken";
import { VError } from "verror";

import {
accessTokenExpirationInMinutesWithrefreshToken,
accessTokenExpirationInHoursWithrefreshToken,
createRefreshJWTToken,
refreshTokenExpirationInHours,
} from "./authenticationUtils";
Expand Down Expand Up @@ -257,9 +257,10 @@ export function addHttpHandler(

if (config.refreshTokenStorage === "memory") {
kvStore.save(
`refreshToken.${refreshToken}`,
`refreshToken.${token.userId}`,
{
userId: token.userId,
token: refreshToken,
},
refreshTokenExpiration,
);
Expand Down Expand Up @@ -299,7 +300,7 @@ export function addHttpHandler(

// conditionally add token expiration to payload
if (config.refreshTokenStorage && ["db", "memory"].includes(config.refreshTokenStorage)) {
body.data.accessTokenExp = 1000 * 60 * accessTokenExpirationInMinutesWithrefreshToken;
body.data.accessTokenExp = 1000 * 60 * 60 * accessTokenExpirationInHoursWithrefreshToken;
}

reply
Expand Down
5 changes: 4 additions & 1 deletion api/src/user_logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import * as jsonwebtoken from "jsonwebtoken";
import { VError } from "verror";

import { config } from "./config";
import { extractUser } from "./handlerUtils";
import { toHttpError } from "./http_errors";
import { AuthenticatedRequest } from "./httpd/lib";
import { Ctx } from "./lib/ctx";
import { kvStore } from "./lib/keyValueStore";
import * as Result from "./result";
Expand Down Expand Up @@ -118,6 +120,7 @@ export function addHttpHandler(
): void {
server.post(`${urlPrefix}/user.logout`, swaggerSchema, async (request, reply) => {
const ctx: Ctx = { requestId: request.id, source: "http" };
const user = extractUser(request as AuthenticatedRequest);
const bodyResult = validateRequestBody(request.body);

if (Result.isErr(bodyResult)) {
Expand All @@ -132,7 +135,7 @@ export function addHttpHandler(

// delete refresh token from storage
if (currentRefreshToken && config.refreshTokenStorage === "memory") {
kvStore.clear(`refreshToken.${currentRefreshToken}`);
kvStore.clear(`refreshToken.${user.id}`);
} else if (currentRefreshToken && config.refreshTokenStorage === "db") {
await service.clearRefreshToken(currentRefreshToken);
}
Expand Down
8 changes: 4 additions & 4 deletions api/src/user_refreshToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Joi = require("joi");
import * as jsonwebtoken from "jsonwebtoken";
import { VError } from "verror";

import { accessTokenExpirationInMinutesWithrefreshToken } from "./authenticationUtils";
import { accessTokenExpirationInHoursWithrefreshToken } from "./authenticationUtils";
import { JwtConfig, config } from "./config";
import { toHttpError } from "./http_errors";
import { AuthenticatedRequest } from "./httpd/lib";
Expand Down Expand Up @@ -220,10 +220,10 @@ export function addHttpHandler(
data: {},
};
// conditionally add token expiration to payload
request.log.warn(`checking accessTokenExp ${config.refreshTokenStorage}`);
request.log.debug(`checking accessTokenExp ${config.refreshTokenStorage}`);
if (config.refreshTokenStorage && ["db", "memory"].includes(config.refreshTokenStorage)) {
request.log.warn("adding accessTokenExp");
body.data.accessTokenExp = 1000 * 60 * accessTokenExpirationInMinutesWithrefreshToken;
request.log.debug("adding accessTokenExp");
body.data.accessTokenExp = 1000 * 60 * 60 * accessTokenExpirationInHoursWithrefreshToken;
}

reply
Expand Down
1 change: 1 addition & 0 deletions scripts/development/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ services:
API_REFRESH_TOKENS_TABLE: ${API_REFRESH_TOKENS_TABLE}
REFRESH_TOKEN_EXPIRATION: ${REFRESH_TOKEN_EXPIRATION}
ACCESS_TOKEN_EXPIRATION: ${ACCESS_TOKEN_EXPIRATION}
IDLE_TIME: ${IDLE_TIME}
command: ["npm", "run", "watch"] # npm run watch: hot reloading
# volume to track code changes
volumes:
Expand Down
1 change: 1 addition & 0 deletions scripts/operation/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ services:
API_REFRESH_TOKENS_TABLE: ${API_REFRESH_TOKENS_TABLE}
REFRESH_TOKEN_EXPIRATION: ${REFRESH_TOKEN_EXPIRATION}
ACCESS_TOKEN_EXPIRATION: ${ACCESS_TOKEN_EXPIRATION}
IDLE_TIME: ${IDLE_TIME}
depends_on:
- alpha-node
volumes:
Expand Down

0 comments on commit c560eee

Please sign in to comment.