Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/omegaquotes/OmegaquotesQuote.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Date from '@/components/Date/Date'
import styles from './OmegaquotesQuote.module.scss'
import Date from '@/components/Date/Date'
import type { OmegaquoteFiltered } from '@/services/omegaquotes/types'

export type OmegaquoteQuotePropTypes = {
Expand Down
61 changes: 48 additions & 13 deletions src/auth/authorizer/AuthResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,33 @@ import type { SessionType, UserGuaranteeOption } from '@/auth/session/Session'

export type AuthStatus = 'AUTHORIZED' | 'UNAUTHORIZED' | 'AUTHORIZED_NO_USER' | 'UNAUTHENTICATED'

export type AuthResultType<UserGuatantee extends UserGuaranteeOption, Authorized extends boolean> = {
export type AuthResultTypeWithoutStatus<
UserGuatantee extends UserGuaranteeOption,
Authorized extends boolean,
PrismaWhereFilter extends object | undefined = undefined
> = {
session: SessionType<UserGuatantee>,
errorMessage?: string,
authorized: Authorized,
status: AuthStatus,
prismaWhereFilter: PrismaWhereFilter | undefined,
}

export type AuthResultTypeAny = AuthResultType<UserGuaranteeOption, boolean>
export type AuthResultType<
UserGuatantee extends UserGuaranteeOption,
Authorized extends boolean,
PrismaWhereFilter extends object | undefined = undefined
> = AuthResultTypeWithoutStatus<UserGuatantee, Authorized, PrismaWhereFilter> & {
status: AuthStatus
}

export type AuthResultTypeAny = AuthResultType<UserGuaranteeOption, boolean, object | undefined>

export class AuthResult<const UserGuatantee extends UserGuaranteeOption, const Authorized extends boolean> {
private authResult: Omit<AuthResultType<UserGuatantee, Authorized>, 'status'>
export class AuthResult<
const UserGuatantee extends UserGuaranteeOption,
const Authorized extends boolean,
const PrismaWhereFilter extends object | undefined = undefined
> {
private authResult: AuthResultTypeWithoutStatus<UserGuatantee, Authorized, PrismaWhereFilter>
public get authorized() {
return this.authResult.authorized
}
Expand All @@ -23,10 +39,20 @@ export class AuthResult<const UserGuatantee extends UserGuaranteeOption, const A
return this.authResult.session
}

public constructor(session: SessionType<UserGuatantee>, authorized: Authorized, errorMessage?: string) {
public get prismaWhereFilter(): PrismaWhereFilter | undefined {
return this.authResult.prismaWhereFilter
}

public constructor(
session: SessionType<UserGuatantee>,
authorized: Authorized,
prismaWhereFilter: PrismaWhereFilter | undefined,
errorMessage?: string
) {
this.authResult = {
session,
authorized,
prismaWhereFilter,
errorMessage,
}
}
Expand All @@ -51,7 +77,7 @@ export class AuthResult<const UserGuatantee extends UserGuaranteeOption, const A
* as you cannot send class instances to a client component from a server component.
* @returns A javascript object representation of the AuthResult
*/
public toJsObject(): AuthResultType<UserGuatantee, Authorized> {
public toJsObject(): AuthResultType<UserGuatantee, Authorized, PrismaWhereFilter> {
return {
session: {
// Note: spread is neccessary if the session stored on the AuthResult is the Session class and
Expand All @@ -61,16 +87,25 @@ export class AuthResult<const UserGuatantee extends UserGuaranteeOption, const A
authorized: this.authorized,
errorMessage: this.getErrorMessage,
status: this.status,
prismaWhereFilter: this.authResult.prismaWhereFilter,
}
}

public static fromJsObject<const UserGuatantee_ extends UserGuaranteeOption, const Authorized_ extends boolean>(
authResult: AuthResultType<UserGuatantee_, Authorized_>
): AuthResult<UserGuatantee_, Authorized_> {
return new AuthResult(authResult.session, authResult.authorized, authResult.errorMessage)
public static fromJsObject<
const UserGuatantee_ extends UserGuaranteeOption,
const Authorized_ extends boolean,
const PrismaWhereFilter_ extends object | undefined = undefined
>(
authResult: AuthResultType<UserGuatantee_, Authorized_, PrismaWhereFilter_>
): AuthResult<UserGuatantee_, Authorized_, PrismaWhereFilter_> {
return new AuthResult(
authResult.session, authResult.authorized, authResult.prismaWhereFilter, authResult.errorMessage
)
}

public redirectOnUnauthorized({ returnUrl }: { returnUrl?: string }) : AuthResult<UserGuatantee, true> {
public redirectOnUnauthorized(
{ returnUrl }: { returnUrl?: string }
) : AuthResult<UserGuatantee, true, PrismaWhereFilter> {
if (!this.authorized) {
if (this.session.user) {
if (!this.session.user.acceptedTerms) {
Expand All @@ -86,6 +121,6 @@ export class AuthResult<const UserGuatantee extends UserGuaranteeOption, const A
}
redirect('/login')
}
return new AuthResult(this.session, true)
return new AuthResult(this.session, true, this.authResult.prismaWhereFilter)
}
}
67 changes: 43 additions & 24 deletions src/auth/authorizer/Authorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,84 @@ export type UserRequieredOutOpt = 'USER_NOT_REQUIERED_FOR_AUTHORIZED' | 'USER_RE

export type AuthorizerDynamicFieldsBound<
UserRequieredOut extends UserRequieredOutOpt = 'USER_NOT_REQUIERED_FOR_AUTHORIZED' | 'USER_REQUIERED_FOR_AUTHORIZED',
PrismaWhereFilter extends object | undefined = undefined
> = {
auth: (session: SessionMaybeUser) => UserRequieredOut extends 'USER_REQUIERED_FOR_AUTHORIZED'
? (AuthResult<'HAS_USER', true> | AuthResult<'HAS_USER' | 'NO_USER', false>)
: (AuthResult<'HAS_USER' | 'NO_USER', true> | AuthResult<'HAS_USER' | 'NO_USER', false>)
? (AuthResult<'HAS_USER', true, PrismaWhereFilter> | AuthResult<'HAS_USER' | 'NO_USER', false, undefined>)
: (AuthResult<'HAS_USER' | 'NO_USER', true, PrismaWhereFilter> | AuthResult<'HAS_USER' | 'NO_USER', false, undefined>)
}

export type AuthorizerStaticFieldsBound<
DynamicFields extends object,
UserRequieredOut extends UserRequieredOutOpt = 'USER_NOT_REQUIERED_FOR_AUTHORIZED' | 'USER_REQUIERED_FOR_AUTHORIZED',
PrismaWhereFilter extends object | undefined = undefined
> = {
dynamicFields: (dynamicFields: DynamicFields) => AuthorizerDynamicFieldsBound<UserRequieredOut>,
dynamicFields: (dynamicFields: DynamicFields) => AuthorizerDynamicFieldsBound<UserRequieredOut, PrismaWhereFilter>,
}

export type Authorizer<
StaticFields extends object,
DynamicFields extends object,
UserRequieredOut extends UserRequieredOutOpt,
PrismaWhereFilter extends object | undefined = undefined
> = {
staticFields: (staticFields: StaticFields) => AuthorizerStaticFieldsBound<DynamicFields, UserRequieredOut>
staticFields: (staticFields: StaticFields) =>
AuthorizerStaticFieldsBound<DynamicFields, UserRequieredOut, PrismaWhereFilter>
}


export function AuthorizerFactory<
StaticFields extends object,
DynamicFields extends object,
const UserRequieredOut extends UserRequieredOutOpt,
const PrismaWhereFilter extends object | undefined = undefined
>(
authCheck: ((f: {
authCheck: ((_: {
session: SessionMaybeUser,
staticFields: StaticFields,
dynamicFields: DynamicFields
}) => UserRequieredOut extends 'USER_REQUIERED_FOR_AUTHORIZED' ? ({
success: true,
session: SessionUser,
errorMessage?: string,
} | {
success: false,
session: SessionMaybeUser
errorMessage?: string,
}) : ({
success: boolean,
session: SessionMaybeUser
errorMessage?: string,
}))
): Authorizer<StaticFields, DynamicFields, UserRequieredOut> {
}) => UserRequieredOut extends 'USER_REQUIERED_FOR_AUTHORIZED' ? (
({
success: true,
session: SessionUser,
errorMessage?: string,
prismaWhereFilter?: PrismaWhereFilter,
} & (PrismaWhereFilter extends undefined ? object : { prismaWhereFilter: PrismaWhereFilter })) | {
success: false,
session: SessionMaybeUser,
errorMessage?: string,
}
) : (
({
success: true,
session: SessionMaybeUser,
errorMessage?: string,
prismaWhereFilter?: PrismaWhereFilter,
} & (PrismaWhereFilter extends undefined ? object : { prismaWhereFilter: PrismaWhereFilter })) | {
success: false,
session: SessionMaybeUser,
errorMessage?: string,
}
)
)
): Authorizer<StaticFields, DynamicFields, UserRequieredOut, PrismaWhereFilter> {
return {
staticFields: (staticFields) => (
{
dynamicFields: (dynamicFields) => (
{
auth: (session) => {
const { session: sessionOut, success, errorMessage } = authCheck({
const results = authCheck({
session, staticFields, dynamicFields
})
if (success) {
return new AuthResult(sessionOut, true)
if (results.success) {
return new AuthResult(
results.session,
true,
results.prismaWhereFilter!
)
}

return new AuthResult(sessionOut, false, errorMessage)
return new AuthResult(results.session, false, undefined, results.errorMessage)
}
}
)
Expand Down
2 changes: 1 addition & 1 deletion src/auth/authorizer/RequireJWT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ export const RequireJWT = AuthorizerFactory<

return {
success: true,
session
session,
}
})
15 changes: 15 additions & 0 deletions src/auth/authorizer/RequireVisibilityFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AuthorizerFactory } from './Authorizer'
import { visibilityFilter } from '@/auth/visibility/visibilityFilter'
import type { Permission } from '@/prisma-generated-pn-types'

export const RequireVisibilityFilter = AuthorizerFactory<
{ bypassPermission: Permission },
Record<string, never>,
'USER_NOT_REQUIERED_FOR_AUTHORIZED',
ReturnType<typeof visibilityFilter> | undefined
> (({ session, staticFields }) => ({
success: true,
prismaWhereFilter:
session.permissions.includes(staticFields.bypassPermission) ? undefined : visibilityFilter(session.memberships),
session,
}))
2 changes: 2 additions & 0 deletions src/auth/visibility/visibilityFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ export function visibilityFilter(memberships: MembershipFiltered[]) {
}
} as const satisfies Prisma.VisibilityWhereInput
}

export type VisibilityFilter = ReturnType<typeof visibilityFilter>
8 changes: 4 additions & 4 deletions src/hooks/useAuthorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ function useAuthorizer({
authorizer
}: {
authorizer: AuthorizerDynamicFieldsBound<'USER_NOT_REQUIERED_FOR_AUTHORIZED'>
}): AuthResult<UserGuaranteeOption, boolean>
}): AuthResult<UserGuaranteeOption, boolean, object | undefined>
function useAuthorizer({
authorizer
}: {
authorizer: AuthorizerDynamicFieldsBound<'USER_REQUIERED_FOR_AUTHORIZED'>
}): AuthResult<UserGuaranteeOption, false> | AuthResult<'HAS_USER', true>
}): AuthResult<UserGuaranteeOption, false, object | undefined> | AuthResult<'HAS_USER', true, object | undefined>
function useAuthorizer({
authorizer
}: {
authorizer: AuthorizerDynamicFieldsBound
}): AuthResult<UserGuaranteeOption, boolean> {
}): AuthResult<UserGuaranteeOption, boolean, object | undefined> {
const session = useSession()
if (session.loading) {
return new AuthResult(Session.empty(), false)
return new AuthResult(Session.empty(), false, undefined)
}
return authorizer.auth(session.session)
}
Expand Down
2 changes: 1 addition & 1 deletion src/prisma/seeder/src/SeedSpecialImageCollections.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PrismaClient } from '@/prisma-generated-pn-client'
import { SpecialCollection } from '@/prisma-generated-pn-types'
import type { PrismaClient } from '@/prisma-generated-pn-client'

export default async function SeedSpecialImageCollections(prisma: PrismaClient) {
const keys = Object.keys(SpecialCollection) as SpecialCollection[]
Expand Down
2 changes: 1 addition & 1 deletion src/prisma/seeder/src/development/seedDevUsers.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { hashAndEncryptPassword } from '@/auth/passwordHash'
import { type SeederImage, seedImage } from '@/seeder/src/seedImages'
import { OmegaMembershipLevel, type Prisma } from '@/prisma-generated-pn-types'
import { v4 as uuid } from 'uuid'
import { randomInt } from 'crypto'
import { readdir } from 'fs/promises'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
import type { PrismaClient } from '@/prisma-generated-pn-client'
import { OmegaMembershipLevel, type Prisma } from '@/prisma-generated-pn-types'

const fileName = fileURLToPath(import.meta.url)
const directoryName = dirname(fileName)
Expand Down
2 changes: 1 addition & 1 deletion src/prisma/seeder/src/dobbelOmega/dobbelOmega.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { UserMigrator } from './migrateUsers'
import migrateCommittees from './migrateCommittees'
import seedProdPermissions from './seedProdPermissions'
import manifest from '@/seeder/src/logger'
import { PrismaClient as PrismaClientOw } from '@/prisma-generated-ow-basic/client'
import { PrismaPg } from '@prisma/adapter-pg'
import type { PrismaClient as PrismaClientPn } from '@/prisma-generated-pn-client'
import { PrismaClient as PrismaClientOw } from '@/prisma-generated-ow-basic/client'

/**
* !DobbelOmega!
Expand Down
2 changes: 1 addition & 1 deletion src/prisma/seeder/src/seedOmegaMembershipGroups.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PrismaClient } from '@/prisma-generated-pn-client'
import { OmegaMembershipLevel } from '@/prisma-generated-pn-types'
import type { PrismaClient } from '@/prisma-generated-pn-client'

export default async function seedOmegaMembershipGroups(prisma: PrismaClient) {
const levels = Object.values(OmegaMembershipLevel)
Expand Down
Loading