From 1036db781f2f0ea1f3a2641b311a6993e78aeb25 Mon Sep 17 00:00:00 2001 From: Rendall Date: Tue, 22 Aug 2023 07:36:45 +0300 Subject: [PATCH 1/6] Change all instances of uuid use From 15ef13b9f4b8fa9c6e4f3d69333a4a1b9fc4fbbf Mon Sep 17 00:00:00 2001 From: Rendall Date: Tue, 22 Aug 2023 07:45:45 +0300 Subject: [PATCH 2/6] Change function name to generateGuestId --- src/lib/backend-utilities.ts | 2 +- src/tests/backend/MongodbService.test.ts | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib/backend-utilities.ts b/src/lib/backend-utilities.ts index f4fb75d..ab765a8 100644 --- a/src/lib/backend-utilities.ts +++ b/src/lib/backend-utilities.ts @@ -40,7 +40,7 @@ const isDefined = (x: T | undefined | null): x is T => x !== undefined && x !== null /** Creates an id specifically for a guest */ -export const createGuestId = () => uuidv4() +export const generateGuestId = () => uuidv4() /** * Returns true if userId is a guest id diff --git a/src/tests/backend/MongodbService.test.ts b/src/tests/backend/MongodbService.test.ts index dd4b5e0..a41b46d 100644 --- a/src/tests/backend/MongodbService.test.ts +++ b/src/tests/backend/MongodbService.test.ts @@ -33,7 +33,7 @@ import { import { getAuthToken, hashPassword } from "../../../src/lib/crypt" import policy from "../../policy.json" import { - createGuestId, + generateGuestId, isComment, isDeletedComment, isDiscussion, @@ -247,14 +247,14 @@ describe("Full API service test", () => { }) test("POST to /user with guest id and admin credentials should fail", async () => { - const guestUser = { ...testNewUser, id: createGuestId() } + const guestUser = { ...testNewUser, id: generateGuestId() } const authUser = getAuthUser(u => u.isAdmin!) expect.assertions(1) const e = await service.userPOST(guestUser, authUser.id) expect(e).toHaveProperty("statusCode", 403) }) test("POST to /user with guest id and same credentials should succeed", () => { - const id = createGuestId() + const id = generateGuestId() const guestUser = { id, name: randomString(alphaUserInput), @@ -341,7 +341,7 @@ describe("Full API service test", () => { test("POST to /user with a guestUserId as targetId should fail", async () => { const newUser = { ...mockUser(), - id: createGuestId(), + id: generateGuestId(), password: mockPassword(), email: mockEmail(), isAdmin: false, @@ -567,7 +567,7 @@ describe("Full API service test", () => { test("Admins should be able to modify guest users", async () => { // PUT to /user/{userId} with userId as guestId should succeed - const id = createGuestId() + const id = generateGuestId() const guestUser = { id, name: randomString(alphaUserInput), @@ -595,7 +595,7 @@ describe("Full API service test", () => { test("Guest users should never be made into admins", async () => { // PUT to /user/{userId} with guestUser changing isAdmin should fail - const id = createGuestId() + const id = generateGuestId() const guestUser = { id, name: randomString(alphaUserInput), @@ -737,7 +737,7 @@ describe("Full API service test", () => { // Comment Read test("GET comment to /comment/{commentId} where commentId does not exist should return 404", async () => { - const parentCommentId = createGuestId() + const parentCommentId = generateGuestId() const user = getAuthUser() expect.assertions(1) const e = await service.commentGET(parentCommentId, user.id) @@ -817,7 +817,7 @@ describe("Full API service test", () => { expect(e).toBe(error403UserNotAuthorized) }) test("PUT comment to /comment/{commentId} where Id does not exist should return 404", async () => { - const unknownComment = { text: randomString(), id: createGuestId() } + const unknownComment = { text: randomString(), id: generateGuestId() } const adminUserTest = authUserTest expect.assertions(1) const e = await service.commentPUT( @@ -1035,7 +1035,7 @@ describe("Full API service test", () => { .catch(e => expect(e).toBe(error403UserNotAuthorized)) }) test("DELETE to /topic/{topicId} where Id does not exist should return 404", () => { - const deleteTopicId = createGuestId() + const deleteTopicId = generateGuestId() const adminUserTest = getAuthUser(u => u.isAdmin!) return service .topicDELETE(deleteTopicId, adminUserTest.id) From e21eeb3988544a93d9a25edbbd093b0133124a25 Mon Sep 17 00:00:00 2001 From: Rendall Date: Tue, 22 Aug 2023 07:53:15 +0300 Subject: [PATCH 3/6] Remove uuid from tests --- src/tests/backend/utilities.test.ts | 15 ++++++++------- src/tests/mockData.ts | 6 ++++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/tests/backend/utilities.test.ts b/src/tests/backend/utilities.test.ts index a62d193..5bed151 100644 --- a/src/tests/backend/utilities.test.ts +++ b/src/tests/backend/utilities.test.ts @@ -1,13 +1,14 @@ import { addHeaders, + generateGuestId, getAllowOriginHeaders, isAllowedReferer, isGuestId, parseQuery, } from "../../../src/lib/backend-utilities" -import { v4 as uuidv4 } from "uuid" import type { Email } from "../../../src/lib/simple-comment-types" import { validateEmail, validateUserId } from "../../lib/shared-utilities" +import { mockUserId } from "../mockData" describe("test the `getAllowOriginHeaders` function", () => { it("should return {headers} if there is a header match", () => { @@ -47,10 +48,10 @@ describe("test the `getAllowOriginHeaders` function", () => { }) describe("Test guest id utility", () => { - test("isGuestId should fail anything other than a uuid", () => { + test("isGuestId should pass id from generateGuestId", () => { //TODO: make this more random expect(isGuestId("rendall")).toBe(false) - const guestId = uuidv4() + const guestId = generateGuestId() expect(isGuestId(guestId)).toBe(true) }) }) @@ -60,8 +61,8 @@ describe("Test validations", () => { expect(validateUserId("rendall-775-id")).toEqual({ isValid: true }) }) - test("uuidv4 in validateUserId", () => { - expect(validateUserId(uuidv4())).toEqual({ isValid: true }) + test("guestId is validateUserId", () => { + expect(validateUserId(generateGuestId())).toEqual({ isValid: true }) }) test("incorrect characters in validateUserId", () => { @@ -80,8 +81,8 @@ describe("Test validations", () => { }) }) - test("too many characters in validateUserId", () => { - const tooMany = uuidv4() + "a" + test("too many characters in validateUserId", () => { + const tooMany = mockUserId(37) expect(tooMany.length).toBeGreaterThan(36) expect(validateUserId(tooMany)).toEqual({ isValid: false, diff --git a/src/tests/mockData.ts b/src/tests/mockData.ts index 621a28c..fd73902 100644 --- a/src/tests/mockData.ts +++ b/src/tests/mockData.ts @@ -15,6 +15,8 @@ const passwordInput = const alphaAscii = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_-" const emailAscii = "abcdefghijklmnopqrstuvwxyz01234567890" +const idCharacters = "abcdefghijklmnopqrstuvwxyz-0123456789" + const randomNumber = (min: number = 1, max: number = 10): number => Math.floor(Math.random() * (max - min)) + min export const randomString = ( @@ -37,8 +39,8 @@ export const mockEmail = (): Email => 3 )}` -export const mockUserId = (): string => - randomString(emailAscii, randomNumber(5, 36)) +export const mockUserId = (length:number = randomNumber(5,36)): string => + randomString(idCharacters, length) export const mockUuid4 = () => { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { From 55b852d5359371c9fd7528480ed6c31a23ccb1e2 Mon Sep 17 00:00:00 2001 From: Rendall Date: Tue, 22 Aug 2023 11:57:13 +0300 Subject: [PATCH 4/6] Remove uuid --- docs/GUEST_AUTHENTICATION_FLOW.md | 2 +- package.json | 4 +-- src/frontend-utilities.ts | 2 +- src/lib/MongodbService.ts | 20 ++++++------ src/lib/backend-utilities.ts | 34 ++++++++++++++++----- src/lib/crypt.ts | 4 --- src/lib/policyEnforcement.ts | 2 +- src/tests/backend/policyEnforcement.test.ts | 3 +- src/tests/backend/utilities.test.ts | 34 +++++++++++++++++++++ src/tests/mockData.ts | 31 ++++++++++++------- yarn.lock | 5 --- 11 files changed, 98 insertions(+), 43 deletions(-) diff --git a/docs/GUEST_AUTHENTICATION_FLOW.md b/docs/GUEST_AUTHENTICATION_FLOW.md index 3a15446..d667f7c 100644 --- a/docs/GUEST_AUTHENTICATION_FLOW.md +++ b/docs/GUEST_AUTHENTICATION_FLOW.md @@ -8,6 +8,6 @@ A hit to the `/verify` endpoint verifies if a user is *identified* or a *guest* An uncredentialed user can acquire guest credentials via the `/gauth` endpoint (*guest auth*). These credentials allow a user to create a *guest user* on the `/user` endpoint. -A *guest user* does not have a password and cannot log in, but can comment according to `policy` settings, and can edit non-admin properties like `name` and `email`. *guest auth* credentials are expected to be stored locally in the visitor's browser and reused. The guest user's id is always a `uuid` and therefore an *identifed user* can never have a `uuid` as its id. +A *guest user* does not have a password and cannot log in, but can comment according to `policy` settings, and can edit non-admin properties like `name` and `email`. *guest auth* credentials are expected to be stored locally in the visitor's browser and reused. The guest user's id is always of the form `guest-xxxxx-xxxxx` and therefore an *identifed user* cannot have this id format. If an *identified user* logs in or is created with *guest auth* credentials of a guest user, that guest user can be linked to the *identified* user. diff --git a/package.json b/package.json index c6651ba..8bf4774 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,7 @@ "@types/bcryptjs": "^2.4.2", "@types/jest": "^29.5.3", "@types/jsonwebtoken": "^8.5.0", - "@types/picomatch": "^2.3.0", - "@types/uuid": "^8.3.0" + "@types/picomatch": "^2.3.0" }, "optionalDependencies": { "cypress": "^12.17.4", @@ -83,7 +82,6 @@ "ts-loader": "^9.3.0", "ts-node": "^10.1.0", "typescript": "^5.1.6", - "uuid": "8.3.2", "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.9.0", "webpack-cli": "^4.9.2", diff --git a/src/frontend-utilities.ts b/src/frontend-utilities.ts index 3b42b35..349218a 100644 --- a/src/frontend-utilities.ts +++ b/src/frontend-utilities.ts @@ -100,7 +100,7 @@ const transliterate = (char: string = ""): string => ) ?? [char, undefined])[0] /** Convert a display name to a default user id */ -export const formatUserId = displayName => { +export const formatUserId = ( displayName:string ) => { return displayName .trim() .toLowerCase() diff --git a/src/lib/MongodbService.ts b/src/lib/MongodbService.ts index 0bcea4d..6e69776 100644 --- a/src/lib/MongodbService.ts +++ b/src/lib/MongodbService.ts @@ -25,21 +25,23 @@ import { MongoClient } from "mongodb" import { Service } from "./Service" import { adminOnlyModifiableUserProperties, + generateCommentId, + generateGuestId, + getAllowedOrigins, + isAllowedReferer, isComment, isDeleted, - isGuestId, isDeletedComment, + isEmail, + isGuestId, + normalizeUrl, toAdminSafeUser, toPublicSafeUser, toSafeUser, toTopic, toUpdatedUser, - validateUser, - isAllowedReferer, - getAllowedOrigins, - isEmail, - normalizeUrl, validateGuestUser, + validateUser, } from "./backend-utilities" import policy from "../policy.json" import { @@ -68,7 +70,7 @@ import { success204CommentUpdated, success204UserUpdated, } from "./messages" -import { comparePassword, getAuthToken, hashPassword, uuidv4 } from "./crypt" +import { comparePassword, getAuthToken, hashPassword } from "./crypt" import * as jwt from "jsonwebtoken" import { isValidResult } from "./shared-utilities" export class MongodbService extends Service { @@ -554,7 +556,7 @@ export class MongodbService extends Service { } const adminSafeUser = toAdminSafeUser(authUser) - const id = uuidv4() + const id = generateCommentId(parentId) const insertComment: Comment = { id, text, @@ -957,7 +959,7 @@ export class MongodbService extends Service { }) return } - const guestUserId = uuidv4() + const guestUserId = generateGuestId() const gauthToken = getAuthToken(guestUserId) resolve({ ...success200OK, body: gauthToken }) }) diff --git a/src/lib/backend-utilities.ts b/src/lib/backend-utilities.ts index ab765a8..eb0e1da 100644 --- a/src/lib/backend-utilities.ts +++ b/src/lib/backend-utilities.ts @@ -1,5 +1,5 @@ /** Server-side utilities. Using any of these in the front end will lead to darkness and despair. */ -import { validate as isUuid } from "uuid" +import crypto from "crypto"; import * as jwt from "jsonwebtoken" import * as dotenv from "dotenv" import * as picomatch from "picomatch" @@ -20,7 +20,6 @@ import type { Email, ValidationResult, } from "./simple-comment-types" -import { uuidv4 } from "./crypt" import urlNormalizer from "normalize-url" import { joinValidations, @@ -39,13 +38,34 @@ const BASIC_SCHEME = "Basic" const isDefined = (x: T | undefined | null): x is T => x !== undefined && x !== null +const dateToString = (d = new Date()) => (d.getFullYear() * 10000 + d.getMonth() * 100 + d.getDate()).toString(36) +const timeToString = (d = new Date()) => (d.getHours() * 10000 + d.getMinutes() * 100 + d.getSeconds()).toString(36) +const generateAlpha = (length = 12) => generateString("abcdefghijklmnopqrstuvwxyz", length) +const generateNumeric = (length = 12) => generateString("0123456789", length) +const generateAlphaNumeric = (length = 12): string => generateString("abcdefghijklmnopqrstuvwxyz0123456789", length) +const generateString = (alpha:string, length = 12, id = ""): string => length <= 0 ? id : generateString(alpha, length - 1, id + alpha.charAt(crypto.randomInt(alpha.length))) + /** Creates an id specifically for a guest */ -export const generateGuestId = () => uuidv4() +export const generateGuestId = () => `guest-${generateAlpha(2)}${generateNumeric(3)}-${dateToString()}` + +export const isGuestId = (userId: UserId) => userId && userId.match(/^guest-[a-z]{2}\d{3}-[a-z0-9]{5}$/) !== null + +/** Creates an id for a comment */ +export const generateCommentId = (parentId = "") => { + const cId = `${generateAlphaNumeric( 3 )}-${timeToString()}-${dateToString()}` + if (parentId==="") return cId + const appendIndex = parentId.lastIndexOf("_") + const pId = parentId.slice(appendIndex + 1) + if (pId ==="") return cId + + // if the commentId will be longer than 36 characters, truncate it + if (pId.length > (36 - cId.length - 1)) { + const to36 = pId.slice(0, 36) + return `${to36.slice(0, -cId.length - 1)}_${cId}` + } -/** - * Returns true if userId is a guest id - */ -export const isGuestId = (userId: UserId) => isUuid(userId) + return `${pId}_${cId}` +} /** * These are user properties that are unsafe to return to admins diff --git a/src/lib/crypt.ts b/src/lib/crypt.ts index 8bc6a2a..bbd99f2 100644 --- a/src/lib/crypt.ts +++ b/src/lib/crypt.ts @@ -1,4 +1,3 @@ -import { v4, validate as isValidUuid } from "uuid" import { hash, compare } from "bcryptjs" import * as jwt from "jsonwebtoken" import * as dotenv from "dotenv" @@ -34,6 +33,3 @@ export const getAuthToken = ( exp: number = getExpirationTime(YEAR_SECONDS) ): string => jwt.sign({ user, exp }, process.env.JWT_SECRET) -export const uuidv4 = () => v4() - -export const isUuid = isValidUuid diff --git a/src/lib/policyEnforcement.ts b/src/lib/policyEnforcement.ts index a9106f1..7faa529 100644 --- a/src/lib/policyEnforcement.ts +++ b/src/lib/policyEnforcement.ts @@ -1,7 +1,7 @@ import type { ActionType, Policy, UserId } from "./simple-comment-types" import { Action } from "./simple-comment-types" import policy from "../policy.json" -import { isGuestId } from "../lib/backend-utilities" +import { isGuestId } from "./backend-utilities" /** Return true iff action can be performed by user according to * policy, false otherwise diff --git a/src/tests/backend/policyEnforcement.test.ts b/src/tests/backend/policyEnforcement.test.ts index 4873a30..6e4f14c 100644 --- a/src/tests/backend/policyEnforcement.test.ts +++ b/src/tests/backend/policyEnforcement.test.ts @@ -1,3 +1,4 @@ +import { generateGuestId } from "../../lib/backend-utilities" import { isUserAllowedTo } from "../../lib/policyEnforcement" import type { Policy, User } from "../../lib/simple-comment-types" import { Action } from "../../lib/simple-comment-types" @@ -16,7 +17,7 @@ const mockPolicy: Policy = { } const mockGuestUser: User = { - id: "6e3c9dd2-fb1f-4c10-8d19-c6bdc0156b07", + id: "guest-ih517-c1m4i", name: "", email: "", } diff --git a/src/tests/backend/utilities.test.ts b/src/tests/backend/utilities.test.ts index 5bed151..264e796 100644 --- a/src/tests/backend/utilities.test.ts +++ b/src/tests/backend/utilities.test.ts @@ -2,6 +2,7 @@ import { addHeaders, generateGuestId, getAllowOriginHeaders, + generateCommentId, isAllowedReferer, isGuestId, parseQuery, @@ -55,6 +56,39 @@ describe("Test guest id utility", () => { expect(isGuestId(guestId)).toBe(true) }) }) +describe('generateCommentId', () => { + + const commentPattern = '[a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{5}'; + test('generates a comment ID with a given parent ID', () => { + const commentId = generateCommentId('topic-1'); + const expectedRegex = new RegExp(`^topic-1_${commentPattern}$`) + expect(commentId).toMatch(expectedRegex); + }); + + test('has parent id in string', () => { + const cId = "tuw-26xt-c1m4i" + const pId = "not_in-string" + const parentId = `${pId}_${cId}` + const commentId = generateCommentId(parentId); + const regex = new RegExp(`^${cId}_${commentPattern}`) + expect(commentId).toContain(cId) + expect(commentId).toMatch(regex); + expect(commentId).not.toContain(pId) + + }) + + test('generates a comment ID without a parent ID', () => { + const commentId = generateCommentId(); + const regex = new RegExp(`^${commentPattern}$`) + expect(commentId).toMatch(regex); + }); + + test('generates unique comment IDs', () => { + const commentId1 = generateCommentId('parent1'); + const commentId2 = generateCommentId('parent2'); + expect(commentId1).not.toBe(commentId2); + }); +}); describe("Test validations", () => { test("good validateUserId", () => { diff --git a/src/tests/mockData.ts b/src/tests/mockData.ts index fd73902..f54b544 100644 --- a/src/tests/mockData.ts +++ b/src/tests/mockData.ts @@ -14,7 +14,7 @@ const passwordInput = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()_+-= " const alphaAscii = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_-" -const emailAscii = "abcdefghijklmnopqrstuvwxyz01234567890" +const base36 = "abcdefghijklmnopqrstuvwxyz01234567890" const idCharacters = "abcdefghijklmnopqrstuvwxyz-0123456789" const randomNumber = (min: number = 1, max: number = 10): number => @@ -34,7 +34,7 @@ export const randomString = ( const randomDate = () => new Date(randomNumber(0, new Date().valueOf())) // Returns a random email that will validate but does not create examples of all possible valid emails export const mockEmail = (): Email => - `${randomString(emailAscii)}@${randomString(emailAscii)}.${randomString( + `${randomString(base36)}@${randomString(base36)}.${randomString( "abcdefghijklmnopqrstuvwxyz", 3 )}` @@ -42,19 +42,28 @@ export const mockEmail = (): Email => export const mockUserId = (length:number = randomNumber(5,36)): string => randomString(idCharacters, length) -export const mockUuid4 = () => { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { - const r = (Math.random() * 16) | 0 - const v = c === "x" ? r : (r & 0x3) | 0x8 - return v.toString(16) - }) -} - export const mockPassword = () => randomString(passwordInput, randomNumber(10, 50)).trim() +const randomAlphaNumeric = (length = 10) => randomString(base36, length) + +const generateMockCommentId = (parentId = "") => { + const cId = `${randomAlphaNumeric( 3 )}-${randomAlphaNumeric(4)}-${randomAlphaNumeric(5)}` + if (parentId==="") return cId + const appendIndex = parentId.lastIndexOf("_") + const pId = parentId.slice(appendIndex + 1) + if (pId ==="") return cId + + // if the commentId will be longer than 36 characters, truncate it + if (pId.length > (36 - cId.length - 1)) { + const to36 = pId.slice(0, 36) + return `${to36.slice(0, -cId.length - 1)}_${cId}` + } + + return `${pId}_${cId}` +} const mockComment = (parentId: TopicId | CommentId, user: User): Comment => ({ - id: mockUuid4(), + id: generateMockCommentId(parentId), parentId, userId: user.id, text: randomString(alphaUserInput, randomNumber(50, 500)), diff --git a/yarn.lock b/yarn.lock index 41ede5d..e883f7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3758,11 +3758,6 @@ resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8" integrity sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g== -"@types/uuid@^8.3.0": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" - integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== - "@types/webidl-conversions@*": version "7.0.0" resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7" From 3d12d858ca9db12d08c946daf15a44e0ddba5248 Mon Sep 17 00:00:00 2001 From: Rendall Date: Tue, 22 Aug 2023 12:07:07 +0300 Subject: [PATCH 5/6] Move isGuestId to shared utlities --- src/lib/MongodbService.ts | 3 +-- src/lib/backend-utilities.ts | 3 +-- src/lib/policyEnforcement.ts | 2 +- src/lib/shared-utilities.ts | 3 +++ src/tests/backend/utilities.test.ts | 3 +-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/MongodbService.ts b/src/lib/MongodbService.ts index 6e69776..b0d89e6 100644 --- a/src/lib/MongodbService.ts +++ b/src/lib/MongodbService.ts @@ -33,7 +33,6 @@ import { isDeleted, isDeletedComment, isEmail, - isGuestId, normalizeUrl, toAdminSafeUser, toPublicSafeUser, @@ -72,7 +71,7 @@ import { } from "./messages" import { comparePassword, getAuthToken, hashPassword } from "./crypt" import * as jwt from "jsonwebtoken" -import { isValidResult } from "./shared-utilities" +import { isGuestId, isValidResult } from "./shared-utilities" export class MongodbService extends Service { private isCrossSite = process.env.IS_CROSS_SITE === "true" private _client: MongoClient diff --git a/src/lib/backend-utilities.ts b/src/lib/backend-utilities.ts index eb0e1da..2f7640a 100644 --- a/src/lib/backend-utilities.ts +++ b/src/lib/backend-utilities.ts @@ -22,6 +22,7 @@ import type { } from "./simple-comment-types" import urlNormalizer from "normalize-url" import { + isGuestId, joinValidations, validateDisplayName, validateEmail, @@ -48,8 +49,6 @@ const generateString = (alpha:string, length = 12, id = ""): string => length <= /** Creates an id specifically for a guest */ export const generateGuestId = () => `guest-${generateAlpha(2)}${generateNumeric(3)}-${dateToString()}` -export const isGuestId = (userId: UserId) => userId && userId.match(/^guest-[a-z]{2}\d{3}-[a-z0-9]{5}$/) !== null - /** Creates an id for a comment */ export const generateCommentId = (parentId = "") => { const cId = `${generateAlphaNumeric( 3 )}-${timeToString()}-${dateToString()}` diff --git a/src/lib/policyEnforcement.ts b/src/lib/policyEnforcement.ts index 7faa529..fe95281 100644 --- a/src/lib/policyEnforcement.ts +++ b/src/lib/policyEnforcement.ts @@ -1,7 +1,7 @@ import type { ActionType, Policy, UserId } from "./simple-comment-types" import { Action } from "./simple-comment-types" import policy from "../policy.json" -import { isGuestId } from "./backend-utilities" +import { isGuestId } from "./shared-utilities" /** Return true iff action can be performed by user according to * policy, false otherwise diff --git a/src/lib/shared-utilities.ts b/src/lib/shared-utilities.ts index 2504272..ea6feff 100644 --- a/src/lib/shared-utilities.ts +++ b/src/lib/shared-utilities.ts @@ -1,5 +1,6 @@ import type { InvalidResult, + UserId, ValidResult, ValidationResult, } from "./simple-comment-types" @@ -89,3 +90,5 @@ export const validateDisplayName = (name: string): ValidationResult => { return { isValid: false, reason: "Display name is too long." } return { isValid: true } } + +export const isGuestId = (userId: UserId) => userId && userId.match(/^guest-[a-z]{2}\d{3}-[a-z0-9]{5}$/) !== null \ No newline at end of file diff --git a/src/tests/backend/utilities.test.ts b/src/tests/backend/utilities.test.ts index 264e796..bd3abba 100644 --- a/src/tests/backend/utilities.test.ts +++ b/src/tests/backend/utilities.test.ts @@ -4,11 +4,10 @@ import { getAllowOriginHeaders, generateCommentId, isAllowedReferer, - isGuestId, parseQuery, } from "../../../src/lib/backend-utilities" import type { Email } from "../../../src/lib/simple-comment-types" -import { validateEmail, validateUserId } from "../../lib/shared-utilities" +import { isGuestId, validateEmail, validateUserId } from "../../lib/shared-utilities" import { mockUserId } from "../mockData" describe("test the `getAllowOriginHeaders` function", () => { From 922b499f3df1fb43c177c44b4ddf36b5452ed1e6 Mon Sep 17 00:00:00 2001 From: Rendall Date: Tue, 22 Aug 2023 12:11:01 +0300 Subject: [PATCH 6/6] format --- src/frontend-utilities.ts | 2 +- src/lib/backend-utilities.ts | 34 +++++++++++++------- src/lib/crypt.ts | 1 - src/lib/shared-utilities.ts | 3 +- src/tests/backend/utilities.test.ts | 48 +++++++++++++++-------------- src/tests/mockData.ts | 12 +++++--- 6 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/frontend-utilities.ts b/src/frontend-utilities.ts index 349218a..78281a9 100644 --- a/src/frontend-utilities.ts +++ b/src/frontend-utilities.ts @@ -100,7 +100,7 @@ const transliterate = (char: string = ""): string => ) ?? [char, undefined])[0] /** Convert a display name to a default user id */ -export const formatUserId = ( displayName:string ) => { +export const formatUserId = (displayName: string) => { return displayName .trim() .toLowerCase() diff --git a/src/lib/backend-utilities.ts b/src/lib/backend-utilities.ts index 2f7640a..ceb02cc 100644 --- a/src/lib/backend-utilities.ts +++ b/src/lib/backend-utilities.ts @@ -1,5 +1,5 @@ /** Server-side utilities. Using any of these in the front end will lead to darkness and despair. */ -import crypto from "crypto"; +import crypto from "crypto" import * as jwt from "jsonwebtoken" import * as dotenv from "dotenv" import * as picomatch from "picomatch" @@ -39,26 +39,38 @@ const BASIC_SCHEME = "Basic" const isDefined = (x: T | undefined | null): x is T => x !== undefined && x !== null -const dateToString = (d = new Date()) => (d.getFullYear() * 10000 + d.getMonth() * 100 + d.getDate()).toString(36) -const timeToString = (d = new Date()) => (d.getHours() * 10000 + d.getMinutes() * 100 + d.getSeconds()).toString(36) -const generateAlpha = (length = 12) => generateString("abcdefghijklmnopqrstuvwxyz", length) +const dateToString = (d = new Date()) => + (d.getFullYear() * 10000 + d.getMonth() * 100 + d.getDate()).toString(36) +const timeToString = (d = new Date()) => + (d.getHours() * 10000 + d.getMinutes() * 100 + d.getSeconds()).toString(36) +const generateAlpha = (length = 12) => + generateString("abcdefghijklmnopqrstuvwxyz", length) const generateNumeric = (length = 12) => generateString("0123456789", length) -const generateAlphaNumeric = (length = 12): string => generateString("abcdefghijklmnopqrstuvwxyz0123456789", length) -const generateString = (alpha:string, length = 12, id = ""): string => length <= 0 ? id : generateString(alpha, length - 1, id + alpha.charAt(crypto.randomInt(alpha.length))) +const generateAlphaNumeric = (length = 12): string => + generateString("abcdefghijklmnopqrstuvwxyz0123456789", length) +const generateString = (alpha: string, length = 12, id = ""): string => + length <= 0 + ? id + : generateString( + alpha, + length - 1, + id + alpha.charAt(crypto.randomInt(alpha.length)) + ) /** Creates an id specifically for a guest */ -export const generateGuestId = () => `guest-${generateAlpha(2)}${generateNumeric(3)}-${dateToString()}` +export const generateGuestId = () => + `guest-${generateAlpha(2)}${generateNumeric(3)}-${dateToString()}` /** Creates an id for a comment */ export const generateCommentId = (parentId = "") => { - const cId = `${generateAlphaNumeric( 3 )}-${timeToString()}-${dateToString()}` - if (parentId==="") return cId + const cId = `${generateAlphaNumeric(3)}-${timeToString()}-${dateToString()}` + if (parentId === "") return cId const appendIndex = parentId.lastIndexOf("_") const pId = parentId.slice(appendIndex + 1) - if (pId ==="") return cId + if (pId === "") return cId // if the commentId will be longer than 36 characters, truncate it - if (pId.length > (36 - cId.length - 1)) { + if (pId.length > 36 - cId.length - 1) { const to36 = pId.slice(0, 36) return `${to36.slice(0, -cId.length - 1)}_${cId}` } diff --git a/src/lib/crypt.ts b/src/lib/crypt.ts index bbd99f2..03a139c 100644 --- a/src/lib/crypt.ts +++ b/src/lib/crypt.ts @@ -32,4 +32,3 @@ export const getAuthToken = ( user: string, exp: number = getExpirationTime(YEAR_SECONDS) ): string => jwt.sign({ user, exp }, process.env.JWT_SECRET) - diff --git a/src/lib/shared-utilities.ts b/src/lib/shared-utilities.ts index ea6feff..c6f7b56 100644 --- a/src/lib/shared-utilities.ts +++ b/src/lib/shared-utilities.ts @@ -91,4 +91,5 @@ export const validateDisplayName = (name: string): ValidationResult => { return { isValid: true } } -export const isGuestId = (userId: UserId) => userId && userId.match(/^guest-[a-z]{2}\d{3}-[a-z0-9]{5}$/) !== null \ No newline at end of file +export const isGuestId = (userId?: UserId) => + userId && userId.match(/^guest-[a-z]{2}\d{3}-[a-z0-9]{5}$/) !== null diff --git a/src/tests/backend/utilities.test.ts b/src/tests/backend/utilities.test.ts index bd3abba..635f9b7 100644 --- a/src/tests/backend/utilities.test.ts +++ b/src/tests/backend/utilities.test.ts @@ -7,7 +7,11 @@ import { parseQuery, } from "../../../src/lib/backend-utilities" import type { Email } from "../../../src/lib/simple-comment-types" -import { isGuestId, validateEmail, validateUserId } from "../../lib/shared-utilities" +import { + isGuestId, + validateEmail, + validateUserId, +} from "../../lib/shared-utilities" import { mockUserId } from "../mockData" describe("test the `getAllowOriginHeaders` function", () => { @@ -55,39 +59,37 @@ describe("Test guest id utility", () => { expect(isGuestId(guestId)).toBe(true) }) }) -describe('generateCommentId', () => { - - const commentPattern = '[a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{5}'; - test('generates a comment ID with a given parent ID', () => { - const commentId = generateCommentId('topic-1'); +describe("generateCommentId", () => { + const commentPattern = "[a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{5}" + test("generates a comment ID with a given parent ID", () => { + const commentId = generateCommentId("topic-1") const expectedRegex = new RegExp(`^topic-1_${commentPattern}$`) - expect(commentId).toMatch(expectedRegex); - }); + expect(commentId).toMatch(expectedRegex) + }) - test('has parent id in string', () => { + test("has parent id in string", () => { const cId = "tuw-26xt-c1m4i" const pId = "not_in-string" const parentId = `${pId}_${cId}` - const commentId = generateCommentId(parentId); + const commentId = generateCommentId(parentId) const regex = new RegExp(`^${cId}_${commentPattern}`) expect(commentId).toContain(cId) - expect(commentId).toMatch(regex); + expect(commentId).toMatch(regex) expect(commentId).not.toContain(pId) - }) - test('generates a comment ID without a parent ID', () => { - const commentId = generateCommentId(); + test("generates a comment ID without a parent ID", () => { + const commentId = generateCommentId() const regex = new RegExp(`^${commentPattern}$`) - expect(commentId).toMatch(regex); - }); - - test('generates unique comment IDs', () => { - const commentId1 = generateCommentId('parent1'); - const commentId2 = generateCommentId('parent2'); - expect(commentId1).not.toBe(commentId2); - }); -}); + expect(commentId).toMatch(regex) + }) + + test("generates unique comment IDs", () => { + const commentId1 = generateCommentId("parent1") + const commentId2 = generateCommentId("parent2") + expect(commentId1).not.toBe(commentId2) + }) +}) describe("Test validations", () => { test("good validateUserId", () => { diff --git a/src/tests/mockData.ts b/src/tests/mockData.ts index f54b544..c16a187 100644 --- a/src/tests/mockData.ts +++ b/src/tests/mockData.ts @@ -39,7 +39,7 @@ export const mockEmail = (): Email => 3 )}` -export const mockUserId = (length:number = randomNumber(5,36)): string => +export const mockUserId = (length: number = randomNumber(5, 36)): string => randomString(idCharacters, length) export const mockPassword = () => @@ -48,14 +48,16 @@ export const mockPassword = () => const randomAlphaNumeric = (length = 10) => randomString(base36, length) const generateMockCommentId = (parentId = "") => { - const cId = `${randomAlphaNumeric( 3 )}-${randomAlphaNumeric(4)}-${randomAlphaNumeric(5)}` - if (parentId==="") return cId + const cId = `${randomAlphaNumeric(3)}-${randomAlphaNumeric( + 4 + )}-${randomAlphaNumeric(5)}` + if (parentId === "") return cId const appendIndex = parentId.lastIndexOf("_") const pId = parentId.slice(appendIndex + 1) - if (pId ==="") return cId + if (pId === "") return cId // if the commentId will be longer than 36 characters, truncate it - if (pId.length > (36 - cId.length - 1)) { + if (pId.length > 36 - cId.length - 1) { const to36 = pId.slice(0, 36) return `${to36.slice(0, -cId.length - 1)}_${cId}` }