Skip to content

Commit

Permalink
Page authenticate role (#3902)
Browse files Browse the repository at this point in the history
  • Loading branch information
siddhsuresh authored Nov 9, 2022
1 parent 8b4bf99 commit 97469a1
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 10 deletions.
6 changes: 6 additions & 0 deletions .changeset/red-gorillas-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@blitzjs/auth": patch
"@blitzjs/next": patch
---

Added option `role` to `authenticate` property of `BlitzPage` to authenticate page with respect to the role of the user. `String` value or `Array` of strings can be passed to authorize users.
5 changes: 3 additions & 2 deletions packages/blitz-auth/src/client/auth-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe("publicDataStore", () => {
ret = data
})
getPublicDataStore().clear()
expect(ret).toEqual({userId: null})
expect(ret).toEqual({userId: null, role: null})
})
})

Expand All @@ -107,7 +107,7 @@ describe("publicDataStore", () => {
it("returns empty data if cookie is falsy", () => {
const ret = getPublicDataStore().getData()

expect(ret).toEqual({userId: null})
expect(ret).toEqual({userId: null, role: null})
})
})
})
Expand All @@ -118,6 +118,7 @@ describe("useSession", () => {
const {result} = renderHook(() => useSession())

expect(result.current).toEqual({
role: null,
isLoading: false,
userId: null,
})
Expand Down
54 changes: 49 additions & 5 deletions packages/blitz-auth/src/client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const parsePublicDataToken = (token: string) => {
}
}

const emptyPublicData: EmptyPublicData = {userId: null}
const emptyPublicData: EmptyPublicData = {userId: null, role: null}

class PublicDataStore {
private eventKey = `${LOCALSTORAGE_PREFIX}publicDataUpdated`
Expand Down Expand Up @@ -171,9 +171,8 @@ export const useSession = (options: UseSessionOptions = {}): ClientSession => {
return session
}

export const useAuthorizeIf = (condition?: boolean) => {
export const useAuthorizeIf = (condition?: boolean, role?: string | Array<string>) => {
const [mounted, setMounted] = useState(false)

useEffect(() => {
setMounted(true)
}, [])
Expand All @@ -183,6 +182,29 @@ export const useAuthorizeIf = (condition?: boolean) => {
error.stack = null!
throw error
}

if (isClient && condition && role && getPublicDataStore().getData().userId && mounted) {
const error = new AuthenticationError()
error.stack = null!
if (!authorizeRole(role, getPublicDataStore().getData().role as string)) {
throw error
}
}
}

const authorizeRole = (role?: string | Array<string>, currentRole?: string) => {
if (role && currentRole) {
if (Array.isArray(role)) {
if (role.includes(currentRole)) {
return true
}
} else {
if (currentRole === role) {
return true
}
}
}
return false
}

export const useAuthorize = () => {
Expand Down Expand Up @@ -224,7 +246,7 @@ export type RedirectAuthenticatedToFn = (

export type BlitzPage<P = {}> = React.ComponentType<P> & {
getLayout?: (component: JSX.Element) => JSX.Element
authenticate?: boolean | {redirectTo?: string | RouteUrlObject}
authenticate?: boolean | {redirectTo?: string | RouteUrlObject; role?: string | Array<string>}
suppressFirstRenderFlicker?: boolean
redirectAuthenticatedTo?: RedirectAuthenticatedTo | RedirectAuthenticatedToFn
}
Expand Down Expand Up @@ -275,7 +297,10 @@ function withBlitzAuthPlugin<TProps = any>(Page: ComponentType<TProps> | BlitzPa

let {authenticate, redirectAuthenticatedTo} = getAuthValues(Page, props)

useAuthorizeIf(authenticate === true)
useAuthorizeIf(
authenticate === true,
!authenticate ? undefined : typeof authenticate === "object" ? authenticate.role : undefined,
)

if (typeof window !== "undefined") {
const publicData = getPublicDataStore().getData()
Expand Down Expand Up @@ -304,6 +329,25 @@ function withBlitzAuthPlugin<TProps = any>(Page: ComponentType<TProps> | BlitzPa
throw error
}
}

if (
authenticate &&
typeof authenticate === "object" &&
authenticate.redirectTo &&
authenticate.role &&
!authorizeRole(authenticate.role, publicData.role as string)
) {
let {redirectTo} = authenticate
if (typeof redirectTo !== "string") {
redirectTo = formatWithValidation(redirectTo)
}
const url = new URL(redirectTo, window.location.href)
url.searchParams.append("next", window.location.pathname)
debug("[BlitzAuthInnerRoot] redirecting to", url.toString())
const error = new RedirectError(url.toString())
error.stack = null!
throw error
}
} else {
debug("[BlitzAuthInnerRoot] logged out")
if (authenticate && typeof authenticate === "object" && authenticate.redirectTo) {
Expand Down
3 changes: 2 additions & 1 deletion packages/blitz-auth/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ export interface Session {

export type PublicData = Session extends {PublicData: unknown}
? Session["PublicData"]
: {userId: unknown}
: {userId: unknown; role?: unknown}

export interface EmptyPublicData extends Partial<Omit<PublicData, "userId">> {
userId: PublicData["userId"] | null
role?: PublicData["role"] | null
}

export interface ClientSession extends EmptyPublicData {
Expand Down
2 changes: 1 addition & 1 deletion packages/blitz-next/src/index-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ type RedirectAuthenticatedToFnCtx = {
type RedirectAuthenticatedToFn = (args: RedirectAuthenticatedToFnCtx) => RedirectAuthenticatedTo
export type BlitzPage<P = {}> = React.ComponentType<P> & {
getLayout?: (component: JSX.Element) => JSX.Element
authenticate?: boolean | {redirectTo?: string | RouteUrlObject}
authenticate?: boolean | {redirectTo?: string | RouteUrlObject; role?: string | Array<string>}
suppressFirstRenderFlicker?: boolean
redirectAuthenticatedTo?: RedirectAuthenticatedTo | RedirectAuthenticatedToFn
}
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 97469a1

Please sign in to comment.