Skip to content

Commit

Permalink
Merge branch 'develop' into 19_overview_users_page_principal
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroenbranje authored Jan 25, 2024
2 parents e1a5501 + 95d5933 commit 9910378
Show file tree
Hide file tree
Showing 19 changed files with 293 additions and 17 deletions.
40 changes: 40 additions & 0 deletions apps/envited.ascs.digital/app/api/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,44 @@ describe('api/utils', () => {
expect(result).toEqual('AscsUser')
})
})

describe('userIsIssuedByLoggedInUser', () => {
it('should check if user is issued by logged in user', () => {
// when ... we want to check if the user is issued by the session user
const user = {
id: 'USER_ID',
issuerId: 'PKH',
}

const session = {
user: {
pkh: 'PKH',
},
}
// then ... we should get the result as expected
const result = SUT.userIsIssuedByLoggedInUser(user)(session)

expect(result).toEqual(true)
})
})

describe('isOwnUser', () => {
it('should check if logged in user is own user', () => {
// when ... we want to check if the user is the same as session user
const user = {
id: 'PKH',
issuerId: 'ISSUER_ID',
}

const session = {
user: {
pkh: 'PKH',
},
}
// then ... we should get the result as expected
const result = SUT.isOwnUser(user)(session)

expect(result).toEqual(true)
})
})
})
10 changes: 9 additions & 1 deletion apps/envited.ascs.digital/app/api/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { pathOr } from 'ramda'
import { equals, pathOr, prop } from 'ramda'

import { Session, User } from '../../common/types/types'

export const extractIdFromCredential = pathOr('', ['credentialSubject', 'id'])

export const extractIssuerIdFromCredential = pathOr('', ['issuer', 'id'])

export const extractTypeFromCredential = pathOr('', ['credentialSubject', 'type'])

export const isOwnUser = (user: User) => (session: Session) =>
equals(prop('id')(user))(pathOr('', ['user', 'pkh'])(session))

export const userIsIssuedByLoggedInUser = (user: User) => (session: Session) =>
equals(prop('issuerId')(user))(pathOr('', ['user', 'pkh'])(session))
22 changes: 18 additions & 4 deletions apps/envited.ascs.digital/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import { getServerSession } from '../../common/auth'
import { getUserById } from '../../common/server'
import { Dashboard } from '../../modules/Dashboard'
import { User } from '../../modules/User'

export default async function Index() {
const session = await getServerSession()
const user = await getUserById('did:pkh:tz:tz1SfdVU1mor3Sgej3FmmwMH4HM1EjTzqqeE')

return session ? (
<Dashboard id={session?.user?.id} address={session?.user?.pkh} role={session?.user?.role} />
) : (
<div>Not logged in</div>
return (
<>
<main>
<div className="mx-auto max-w-6xl">
{session ? (
<>
<User {...user} />
<Dashboard id={session?.user?.id} address={session?.user?.pkh} role={session?.user?.role} />
</>
) : (
<div>Not logged in</div>
)}
</div>
</main>
</>
)
}
11 changes: 11 additions & 0 deletions apps/envited.ascs.digital/app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use client'

export default function Error() {
return (
<html>
<body>
<h2>Ah oh, Something went wrong!</h2>
</body>
</html>
)
}
4 changes: 2 additions & 2 deletions apps/envited.ascs.digital/common/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ export const authOptions: NextAuthOptions = {
return match(pkh)
.with('tz1USER', () => ({
id: '1',
pkh: 'tz1USER',
pkh: 'did:pkh:tz:tz1SfdVU1mor3Sgej3FmmwMH4HM1EjTzqqeE',
role: Role.user,
}))
.with('tz1PRINCIPAL', () => ({
id: '1',
pkh: 'tz1PRINCIPAL',
pkh: 'did:pkh:tz:tz1bpeJArd7apJyTUryfXH1SD6w8GL6Gwhj8',
role: Role.principal,
}))
.with('tz1NO_USER', () => null)
Expand Down
6 changes: 4 additions & 2 deletions apps/envited.ascs.digital/common/auth/session.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { getServerSession as NAGetServerSession, NextAuthOptions } from 'next-auth'

import { Session } from '../types/types'
import { authOptions } from './auth'

export const _getServerSession = (NAGetServerSession: any) => (authOptions: NextAuthOptions) => () =>
NAGetServerSession(authOptions)
export const _getServerSession =
(NAGetServerSession: (arg0: NextAuthOptions) => Promise<Session | null>) => (authOptions: NextAuthOptions) => () =>
NAGetServerSession(authOptions)

export const getServerSession = _getServerSession(NAGetServerSession)(authOptions)
4 changes: 4 additions & 0 deletions apps/envited.ascs.digital/common/constants/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ export const RESPONSES = {
status: 204,
statusText: 'No content',
},
unauthorized: {
status: 401,
statusText: 'Unauthorized',
},
}
2 changes: 1 addition & 1 deletion apps/envited.ascs.digital/common/database/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import * as schema from './schema'

export type DatabaseConnection = PostgresJsDatabase<typeof schema>

export type Database = () => Promise<{ [x: string]: any }>
export interface Issuer {
id: string
type: string
Expand Down
1 change: 1 addition & 0 deletions apps/envited.ascs.digital/common/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { getUserById } from './server'
94 changes: 94 additions & 0 deletions apps/envited.ascs.digital/common/server/server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as SUT from './server'

describe('common/server/server', () => {
it('should return a user as expected', async () => {
// when ... we request a user by id
// then ... it returns a user as expected

const getServerSessionStub = jest.fn().mockImplementation(() =>
Promise.resolve({
user: {
pkh: 'USER_PKH',
},
}),
)
const user = {
id: 'USER_PKH',
name: 'USER_NAME',
email: 'USER_EMAIL',
vatId: 'USER_VAT_ID',
privacyPolicyAccepted: 'USER_PRIVACY_POLICY_ACCEPTED',
articlesOfAssociationAccepted: 'USER_ARTICLES_OF_ASSOCIATION_ACCEPTED',
contributionRulesAccepted: 'USER_CONTRIBUTION_RULES_ACCEPTED',
isAscsMember: true,
isEnvitedMember: true,
}
const dbStub = jest.fn().mockImplementation(() =>
Promise.resolve({
getUserById: () => Promise.resolve([user]),
}),
)

const result = await SUT._getUserById({ db: dbStub, getServerSession: getServerSessionStub })('USER_ID')
expect(result).toEqual(user)
})

it('should throw because of missing session', async () => {
// when ... we request a user by id without a session
// then ... it throws as expected
const getServerSessionStub = jest.fn().mockImplementation(() => Promise.resolve(null))
const user = {
id: 'USER_PKH',
name: 'USER_NAME',
email: 'USER_EMAIL',
vatId: 'USER_VAT_ID',
privacyPolicyAccepted: 'USER_PRIVACY_POLICY_ACCEPTED',
articlesOfAssociationAccepted: 'USER_ARTICLES_OF_ASSOCIATION_ACCEPTED',
contributionRulesAccepted: 'USER_CONTRIBUTION_RULES_ACCEPTED',
isAscsMember: true,
isEnvitedMember: true,
}
const dbStub = jest.fn().mockImplementation(() =>
Promise.resolve({
getUserById: () => Promise.resolve([user]),
}),
)

await expect(SUT._getUserById({ db: dbStub, getServerSession: getServerSessionStub })('USER_ID')).rejects.toThrow(
'Something went wrong',
)
})

it('should throw because requester is not allowed to get this resource', async () => {
// when ... we request a user by id, but the requested user is not issued by the requester OR is not their own user
// then ... it throws as expected
const getServerSessionStub = jest.fn().mockImplementation(() =>
Promise.resolve({
user: {
pkh: 'ISSUER_PKH',
},
}),
)
const user = {
id: 'USER_PKH',
issuerId: 'FEDERATOR_PKH',
name: 'USER_NAME',
email: 'USER_EMAIL',
vatId: 'USER_VAT_ID',
privacyPolicyAccepted: 'USER_PRIVACY_POLICY_ACCEPTED',
articlesOfAssociationAccepted: 'USER_ARTICLES_OF_ASSOCIATION_ACCEPTED',
contributionRulesAccepted: 'USER_CONTRIBUTION_RULES_ACCEPTED',
isAscsMember: true,
isEnvitedMember: true,
}
const dbStub = jest.fn().mockImplementation(() =>
Promise.resolve({
getUserById: () => Promise.resolve([user]),
}),
)

await expect(SUT._getUserById({ db: dbStub, getServerSession: getServerSessionStub })('USER_ID')).rejects.toThrow(
'Something went wrong',
)
})
})
35 changes: 35 additions & 0 deletions apps/envited.ascs.digital/common/server/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { isNil } from 'ramda'

import { isOwnUser, userIsIssuedByLoggedInUser } from '../../app/api/utils'
import { getServerSession } from '../auth'
import { db } from '../database/queries'
import { Database } from '../database/types'
import { User } from '../types'
import { Session } from '../types/types'
import { badRequestError, error, unauthorizedError } from '../utils'

export const _getUserById =
({ db, getServerSession }: { db: Database; getServerSession: () => Promise<Session | null> }) =>
async (id: string): Promise<User> => {
try {
const session = await getServerSession()

if (isNil(session)) {
throw unauthorizedError()
}

const connection = await db()
const [user] = await connection.getUserById(id)

if (!userIsIssuedByLoggedInUser(user)(session) && !isOwnUser(user)(session)) {
throw badRequestError()
}

return user
} catch (e) {
console.log('error', e)
throw error()
}
}

export const getUserById = _getUserById({ db, getServerSession })
2 changes: 1 addition & 1 deletion apps/envited.ascs.digital/common/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { Language, Columns, Size, ColorScheme, Role, CredentialType } from './types'
export type { Action, Obj } from './types'
export type { Action, Obj, User } from './types'
16 changes: 12 additions & 4 deletions apps/envited.ascs.digital/common/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ export interface User {
addressCountry: string
addressLocality: string
addressTypeId: string
articlesOfAssociationAccepted: string
contributionRulesAccepted: string
articlesOfAssociationAccepted?: string
contributionRulesAccepted?: string
createdAt: string
email: string
email?: string
expirationDate: string
isAscsMember: boolean
isEnvitedMember: boolean
Expand All @@ -70,5 +70,13 @@ export interface User {
privacyPolicyAccepted: string
streetAddress: string
updatedAt: string
vatId: string
vatId?: string
}

export interface Session {
user: {
pkh: string
id: string
role: Role
}
}
9 changes: 9 additions & 0 deletions apps/envited.ascs.digital/common/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const unauthorized = () => new Error('Unauthorized')

export const badRequest = () => new Error('Bad Request')

export const internalServerError = () => new Error('Internal Server Error')

export const notFound = () => new Error('Not Found')

export const error = () => new Error('Something went wrong')
9 changes: 8 additions & 1 deletion apps/envited.ascs.digital/common/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export { ok, badRequest, noContent, notFound, internalServerError } from './utils'
export { ok, badRequest, noContent, notFound, internalServerError, unauthorized } from './utils'
export {
badRequest as badRequestError,
internalServerError as internalServerErrorError,
notFound as notFoundError,
unauthorized as unauthorizedError,
error,
} from './errors'
3 changes: 3 additions & 0 deletions apps/envited.ascs.digital/common/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ export const notFound = () =>

export const noContent = () =>
new Response(null, { status: RESPONSES.noContent.status, statusText: RESPONSES.noContent.statusText })

export const unauthorized = () =>
new Response(null, { status: RESPONSES.unauthorized.status, statusText: RESPONSES.unauthorized.statusText })
4 changes: 3 additions & 1 deletion apps/envited.ascs.digital/modules/Dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export const Dashboard: FC<DashboardProps> = ({ id, address, role }) => {
<dt className="font-bold">Role</dt>
<dd className="ml-5 italic">{role}</dd>
</dl>
<Button onClick={signOut}>Sign out</Button>
<div className="flex gap-x-3">
<Button onClick={signOut}>Sign out</Button>
</div>
</div>
)
}
37 changes: 37 additions & 0 deletions apps/envited.ascs.digital/modules/User/User.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FC } from 'react'

import { User as IUser } from '../../common/types'

export const User: FC<IUser> = ({
id,
issuerId,
// addressCountry,
// addressLocality,
// addressTypeId,
// articlesOfAssociationAccepted,
// contributionRulesAccepted,
// createdAt,
// email,
// expirationDate,
// isAscsMember,
// isEnvitedMember,
// issuanceDate,
// name,
// postalCode,
// privacyPolicyAccepted,
// streetAddress,
// updatedAt,
// vatId,
}) => {
return (
<div>
<h1 className="text-3xl font-bold mb-5">User data:</h1>
<dl className="mb-10">
<dt className="font-bold">User</dt>
<dd className="ml-5 italic">{id}</dd>
<dt className="font-bold">Issued by</dt>
<dd className="ml-5 italic">{issuerId}</dd>
</dl>
</div>
)
}
1 change: 1 addition & 0 deletions apps/envited.ascs.digital/modules/User/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { User } from './User'

0 comments on commit 9910378

Please sign in to comment.