Skip to content

Commit

Permalink
fix: added linting, added validation for grant cookie and several test
Browse files Browse the repository at this point in the history
  • Loading branch information
johannes-lindgren committed Sep 12, 2022
1 parent f92b4ed commit 65f3e09
Show file tree
Hide file tree
Showing 67 changed files with 1,224 additions and 645 deletions.
16 changes: 8 additions & 8 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export default {
preset: 'ts-jest',
testEnvironment: 'node',
// testEnvironment: 'jsdom',
"moduleNameMapper": {
"^@src(.*)$": "<rootDir>/src$1"
},
testRegex: 'src/.*\\.test\\.(js|jsx|ts|tsx)$'
}
preset: 'ts-jest',
testEnvironment: 'node',
// testEnvironment: 'jsdom',
moduleNameMapper: {
'^@src(.*)$': '<rootDir>/src$1',
},
testRegex: 'src/.*\\.test\\.(js|jsx|ts|tsx)$',
}
57 changes: 30 additions & 27 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
// import typescript from '@rollup/plugin-typescript';
import typescript from 'rollup-plugin-typescript2'
import resolve from '@rollup/plugin-node-resolve';
import external from 'rollup-plugin-peer-deps-external';
import commonjs from '@rollup/plugin-commonjs';
import {visualizer} from 'rollup-plugin-visualizer'
import summary from "rollup-plugin-summary";
import json from '@rollup/plugin-json';
import resolve from '@rollup/plugin-node-resolve'
import external from 'rollup-plugin-peer-deps-external'
import commonjs from '@rollup/plugin-commonjs'
import { visualizer } from 'rollup-plugin-visualizer'
import summary from 'rollup-plugin-summary'
import json from '@rollup/plugin-json'
import packageJson from './package.json'

export default ({
input: `./src/index.ts`,
output: [{
file: packageJson.module,
format: 'esm',
sourcemap: true,
}, {
file: packageJson.main,
format: 'cjs',
sourcemap: true,
}],
plugins: [
external(),
resolve(),
json(),
commonjs(),
typescript(),
summary(),
visualizer(),
]
})
export default {
input: `./src/index.ts`,
output: [
{
file: packageJson.module,
format: 'esm',
sourcemap: true,
},
{
file: packageJson.main,
format: 'cjs',
sourcemap: true,
},
],
plugins: [
external(),
resolve(),
json(),
commonjs(),
typescript(),
summary(),
visualizer(),
],
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './storyblok-auth-api'
export * from './session'
export * from './settings'
export * from './settings'
136 changes: 70 additions & 66 deletions src/session/app-session-cookie-store.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,90 @@
import {AppSessionStore} from "@src/session/app-session-store";
import {AppSession, AppSessionKeys, AppSessionQuery} from "@src/session/app-session";
import {RequestParams} from "@src/session/request-params";
import {AppParams} from "@src/storyblok-auth-api/params/app-params";
import {getSignedCookie} from "@src/utils/signed-cookie/get-signed-cookie";
import {setSignedCookie} from "@src/utils/signed-cookie/set-signed-cookie";
import { AppSessionStore } from '@src/session/app-session-store'
import {
AppSession,
AppSessionKeys,
AppSessionQuery,
} from '@src/session/app-session'
import { RequestParams } from '@src/session/request-params'
import { AppParams } from '@src/storyblok-auth-api/params/app-params'
import { getSignedCookie } from '@src/utils/signed-cookie/get-signed-cookie'
import { setSignedCookie } from '@src/utils/signed-cookie/set-signed-cookie'

export type AppSessionCookieStoreFactory = (staticParams: CookieParams & AppParams) =>
(requestParams: RequestParams) => AppSessionStore
export type AppSessionCookieStoreFactory = (
staticParams: CookieParams & AppParams,
) => (requestParams: RequestParams) => AppSessionStore

export type CookieParams = {
jwtSecret: string
// The name of the cookie that will be issued by this api endpoint handler
cookieName: string
jwtSecret: string
// The name of the cookie that will be issued by this api endpoint handler
cookieName: string
}

const toKeys = (keys: AppSessionQuery): AppSessionKeys => {
const {spaceId, userId} = keys
return ({
spaceId: typeof spaceId === 'number' ? spaceId : parseInt(spaceId, 10),
userId: typeof userId === 'number' ? userId : parseInt(userId, 10),
})
const { spaceId, userId } = keys
return {
spaceId: typeof spaceId === 'number' ? spaceId : parseInt(spaceId, 10),
userId: typeof userId === 'number' ? userId : parseInt(userId, 10),
}
}

export const isAppSessionQuery = (obj: unknown): obj is AppSessionQuery => {
if (!(
typeof obj === 'object' && obj !== null && 'userId' in obj && 'spaceId' in obj
)) {
return false
}
const r = obj as Record<string, unknown>
return (
typeof r.userId === 'string' ||
typeof r.userId === 'number'
) && (
typeof r.spaceId === 'string' ||
typeof r.spaceId === 'number'
if (
!(
typeof obj === 'object' &&
obj !== null &&
'userId' in obj &&
'spaceId' in obj
)
) {
return false
}
const r = obj as Record<string, unknown>
return (
(typeof r.userId === 'string' || typeof r.userId === 'number') &&
(typeof r.spaceId === 'string' || typeof r.spaceId === 'number')
)
}
type AppSessionCookiePayload = {
sessions: AppSession[]
sessions: AppSession[]
}

export const simpleSessionCookieStore: AppSessionCookieStoreFactory =
(params) =>
(requestParams) => {
const {cookieName, appClientId, jwtSecret} = params
const {req, res} = requestParams
const getCookie = getSignedCookie(jwtSecret)<AppSessionCookiePayload>(cookieName)
const setCookie = setSignedCookie(jwtSecret)<AppSessionCookiePayload>(cookieName)
const getSessions = () => (getCookie(req) ?? {sessions: []}).sessions
return {
get: async (params) => (
getSessions().find(matches({...toKeys(params), appClientId}))
),
getAll: async () => (
getSessions().filter(session => (
session.appClientId === appClientId
))
),
put: async (session) => {
const filter = matches(session)
const otherSessions = getSessions().filter(s => !filter(s))
const allSessions = [
...otherSessions,
session,
]
(params) => (requestParams) => {
const { cookieName, appClientId, jwtSecret } = params
const { req, res } = requestParams
const getCookie =
getSignedCookie(jwtSecret)<AppSessionCookiePayload>(cookieName)
const setCookie =
setSignedCookie(jwtSecret)<AppSessionCookiePayload>(cookieName)
const getSessions = () => (getCookie(req) ?? { sessions: [] }).sessions
return {
get: async (params) =>
getSessions().find(matches({ ...toKeys(params), appClientId })),
getAll: async () =>
getSessions().filter((session) => session.appClientId === appClientId),
put: async (session) => {
const filter = matches(session)
const otherSessions = getSessions().filter((s) => !filter(s))
const allSessions = [...otherSessions, session]

setCookie({sessions: allSessions})(res)
return session
},
remove: async (params) => {
const sessions = getSessions()
const toRemove = sessions.find(matches({...toKeys(params), appClientId}))
const allOther = sessions.filter(s => s !== toRemove)
setCookie({sessions: allOther})(res)
return toRemove
}
}
}
setCookie({ sessions: allSessions })(res)
return session
},
remove: async (params) => {
const sessions = getSessions()
const toRemove = sessions.find(
matches({ ...toKeys(params), appClientId }),
)
const allOther = sessions.filter((s) => s !== toRemove)
setCookie({ sessions: allOther })(res)
return toRemove
},
}
}

const matches = (a: AppSessionKeys & { appClientId: string }) => (b: AppSessionKeys & { appClientId: string }) => (
const matches =
(a: AppSessionKeys & { appClientId: string }) =>
(b: AppSessionKeys & { appClientId: string }) =>
a.appClientId === b.appClientId &&
a.spaceId === b.spaceId &&
a.userId === b.userId
)
17 changes: 7 additions & 10 deletions src/session/app-session-refresh-times.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import {AppSession} from "@src/session/app-session";
import { AppSession } from '@src/session/app-session'

export const shouldRefresh = (session: AppSession): boolean => (
serverRefreshIn(session) < 0
)
export const shouldRefresh = (session: AppSession): boolean =>
serverRefreshIn(session) < 0

export const serverRefreshIn = (session: AppSession): number => (
expiresIn(session) - 60 * 1000
)
export const serverRefreshIn = (session: AppSession): number =>
expiresIn(session) - 60 * 1000

export const expiresIn = (session: AppSession): number => (
session.expiresAt - Date.now()
)
export const expiresIn = (session: AppSession): number =>
session.expiresAt - Date.now()
26 changes: 11 additions & 15 deletions src/session/app-session-store.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import {AppSession, AppSessionQuery} from "@src/session/app-session";
import { AppSession, AppSessionQuery } from '@src/session/app-session'

export type AppSessionStore = {
get: (
keys: AppSessionQuery,
options?: {
autoRefresh?: boolean
}
) => Promise<AppSession | undefined>
getAll: () => Promise<AppSession[]>
put: (
session: AppSession,
) => Promise<AppSession | undefined>
remove: (
keys: AppSessionQuery
) => Promise<AppSession | undefined>
}
get: (
keys: AppSessionQuery,
options?: {
autoRefresh?: boolean
},
) => Promise<AppSession | undefined>
getAll: () => Promise<AppSession[]>
put: (session: AppSession) => Promise<AppSession | undefined>
remove: (keys: AppSessionQuery) => Promise<AppSession | undefined>
}
35 changes: 20 additions & 15 deletions src/session/app-session.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@

export type AppSessionKeys = Pick<AppSession, 'spaceId' | 'userId' >
export type AppSessionQueryParams = Record<keyof Pick<AppSession, 'spaceId' | 'userId' >, string>
export type AppSessionQuery = Record<keyof Pick<AppSession, 'spaceId' | 'userId' >, string | number>
export type AppSessionKeys = Pick<AppSession, 'spaceId' | 'userId'>
export type AppSessionQueryParams = Record<
keyof Pick<AppSession, 'spaceId' | 'userId'>,
string
>
export type AppSessionQuery = Record<
keyof Pick<AppSession, 'spaceId' | 'userId'>,
string | number
>

export type AppSession = {
// sessionId: string // TODO, do we need this? Before reading from db, must know if user is authorized
spaceId: number // primary key
userId: number // primary key
appClientId: string // primary key
userName: string
spaceName: string
roles: string[]
expiresAt: number
refreshToken: string
accessToken: string
// sessionId: string // TODO, do we need this? Before reading from db, must know if user is authorized
spaceId: number // primary key
userId: number // primary key
appClientId: string // primary key
userName: string
spaceName: string
roles: string[]
expiresAt: number
refreshToken: string
accessToken: string
}

// TODO decide whether to use this schema instead?
Expand All @@ -23,4 +28,4 @@ export type AppSession = {
// expiresAt: number
// refreshToken: string
// accessToken: string
// } & UserInfo
// } & UserInfo
33 changes: 17 additions & 16 deletions src/session/refresh-app-session.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import {refreshToken} from "@src/storyblok-auth-api/refresh-token";
import {AppSession} from "@src/session/app-session";
import {AppParams} from "@src/storyblok-auth-api/params/app-params";
import { refreshToken } from '@src/storyblok-auth-api/refresh-token'
import { AppSession } from '@src/session/app-session'
import { AppParams } from '@src/storyblok-auth-api/params/app-params'

/**
* Returns a new session that is refreshed
* @return a session that is refreshed
* @param params
*/
export const refreshAppSession = (params: AppParams) =>
async (oldSession: AppSession): Promise<AppSession | undefined> => {
if (!oldSession.refreshToken) {
return undefined
}
return refreshToken(params)(oldSession.refreshToken).then(
({access_token, expires_in}) => ({
...oldSession,
accessToken: access_token,
expiresAt: Date.now() + expires_in * 1000
})
).catch(() => undefined)
}
export const refreshAppSession =
(params: AppParams) =>
async (oldSession: AppSession): Promise<AppSession | undefined> => {
if (!oldSession.refreshToken) {
return undefined
}
return refreshToken(params)(oldSession.refreshToken)
.then(({ access_token, expires_in }) => ({
...oldSession,
accessToken: access_token,
expiresAt: Date.now() + expires_in * 1000,
}))
.catch(() => undefined)
}
Loading

0 comments on commit 65f3e09

Please sign in to comment.