Skip to content

Commit

Permalink
Org group entity, fix auth, add identity (#11)
Browse files Browse the repository at this point in the history
* org group entity

* add identity creation from snap

* get providers identity details in auth flow

* hotfix
  • Loading branch information
lukachi authored Dec 29, 2023
1 parent 5212b20 commit 5ff2836
Show file tree
Hide file tree
Showing 15 changed files with 178 additions and 72 deletions.
27 changes: 7 additions & 20 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { AppRoutes } from './routes'
const App: FC<HTMLAttributes<HTMLDivElement>> = () => {
const [isAppInitialized, setIsAppInitialized] = useState(false)

const { provider, isValidChain, init: initWeb3 } = useWeb3Context()
const { provider, isValidChain } = useWeb3Context()
const { theme } = useThemeMode()
const { checkMetamaskExists, checkSnapExists, connectOrInstallSnap } = useMetamaskZkpSnapContext()
const { checkSnapStatus } = useMetamaskZkpSnapContext()
const { authorize } = useAuth()

useViewportSizes()
Expand All @@ -27,30 +27,17 @@ const App: FC<HTMLAttributes<HTMLDivElement>> = () => {
if (provider?.address) return

try {
if (await checkMetamaskExists()) {
/**
* We don't pass providerType here,
* because only want to check is user was connected before
*/
await initWeb3()
if (await checkSnapExists()) {
await connectOrInstallSnap()
await authorize()
}
const { isMetamaskInstalled, isSnapInstalled } = await checkSnapStatus()

if (isMetamaskInstalled && isSnapInstalled) {
await authorize()
}
} catch (error) {
ErrorHandler.processWithoutFeedback(error)
}

setIsAppInitialized(true)
}, [
provider?.address,
checkMetamaskExists,
initWeb3,
checkSnapExists,
connectOrInstallSnap,
authorize,
])
}, [provider?.address, checkSnapStatus, authorize])

useEffect(() => {
init()
Expand Down
2 changes: 2 additions & 0 deletions src/api/modules/orgs/enums/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './org-groups'
export * from './orgs'
3 changes: 3 additions & 0 deletions src/api/modules/orgs/enums/org-groups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum OrgGroupIncludes {
GroupUsers = 'group_users',
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ export enum OrgsRequestFilters {
}

export enum OrgsIncludes {
organization = 'organization',
owner = 'owner',
Organization = 'Organization',
Owner = 'Owner',
}
2 changes: 2 additions & 0 deletions src/api/modules/orgs/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './org-groups'
export * from './orgs'
33 changes: 33 additions & 0 deletions src/api/modules/orgs/helpers/org-groups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { api, OrgGroup, OrgGroupCreate, OrgGroupRequestQueryParams } from '@/api'

export const loadOrgGroups = async (orgId: string, query?: OrgGroupRequestQueryParams) => {
const { data } = await api.get<OrgGroup[]>(`/v1/orgs/${orgId}/groups`, {
query,
})

return data
}

export const createOrgGroup = async (orgId: string, createOpts: OrgGroupCreate) => {
const { data } = await api.post<OrgGroup>(`/v1/orgs/${orgId}/groups`, {
body: {
data: {
...createOpts,
},
},
})

return data
}

export const loadOrgGroupById = async (
orgId: string,
groupId: string,
query?: OrgGroupRequestQueryParams,
) => {
const { data } = await api.get<OrgGroup>(`/v1/orgs/${orgId}/groups/${groupId}`, {
query,
})

return data
}
File renamed without changes.
2 changes: 1 addition & 1 deletion src/api/modules/orgs/hooks/org.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const useOrg = (id: string) => {

const loadOrg = useCallback(async () => {
return loadOrgById(id, {
include: OrgsIncludes.owner,
include: OrgsIncludes.Owner,
})
}, [id])

Expand Down
2 changes: 2 additions & 0 deletions src/api/modules/orgs/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './org-groups'
export * from './orgs'
47 changes: 47 additions & 0 deletions src/api/modules/orgs/types/org-groups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { OrgGroupIncludes } from '@/api'

export type OrgGroupMetadata = {
name: string
description: string
// TODO: other metadata e. g. styles
}

export type OrgGroupRule = {
scheme: string
required: boolean
}

export type GroupUser = {
id: string
type: 'group-users'
group_id: string
user_id: string
role: {
name: string
value: number
}
created_at: string
updated_at: string
}

export type OrgGroup = {
id: string
type: 'groups'
org_id: string
metadata: OrgGroupMetadata
rules: OrgGroupRule[]
created_at: string
group_users: GroupUser[]
}

export type OrgGroupCreate = {
type: 'groups-create'
attributes: {
metadata: OrgGroupMetadata
rules: OrgGroupRule[]
}
}

export type OrgGroupRequestQueryParams = {
include?: OrgGroupIncludes
}
File renamed without changes.
64 changes: 42 additions & 22 deletions src/contexts/metamask-zkp-snap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,27 @@ interface MetamaskZkpSnapContextValue {

isLocalSnap: (snapId: string) => boolean

createIdentity: () => Promise<{
createIdentity: (connector?: SnapConnector) => Promise<{
identityIdString: string
identityIdBigIntString: string
}>
getVerifiableCredentials: (
params: SaveCredentialsRequestParams,
connector?: SnapConnector,
) => Promise<W3CCredential[] | undefined>
createProof: (params: CreateProofRequestParams) => Promise<ZKPProofResponse | undefined>
checkMetamaskExists: () => Promise<boolean>
createProof: (
params: CreateProofRequestParams,
connector?: SnapConnector,
) => Promise<ZKPProofResponse | undefined>
checkMetamaskExists: (connector?: SnapConnector) => Promise<boolean>
checkSnapExists: () => Promise<boolean>
checkSnapStatus: () => Promise<{
isMetamaskInstalled: boolean
isSnapInstalled: boolean
}>

connectOrInstallSnap: () => Promise<void>
getCredentials: () => Promise<W3CCredential[]>
connectOrInstallSnap: () => Promise<SnapConnector>
getCredentials: (connector?: SnapConnector) => Promise<W3CCredential[]>
}

const CONTEXT_NOT_INITIALIZED_ERROR = new ReferenceError('MetamaskZkpSnapContext not initialized')
Expand Down Expand Up @@ -95,43 +99,57 @@ export const MetamaskZkpSnapContextProvider: FC<HTMLAttributes<HTMLDivElement>>
* create identity and return did if it doesn't exist
* or return the existing one
*/
const createIdentity = useCallback(async () => {
if (!connector) throw new TypeError('Connector is not defined')
const createIdentity = useCallback(
async (_connector?: SnapConnector) => {
const currentConnector = _connector || connector

const identity = await connector.createIdentity()
if (!currentConnector) throw new TypeError('Connector is not defined')

setUserDid(identity.identityIdString)
setUserDidBigIntString(identity.identityIdBigIntString)
const identity = await currentConnector.createIdentity()

return identity
}, [connector])
setUserDid(identity.identityIdString)
setUserDidBigIntString(identity.identityIdBigIntString)

return identity
},
[connector],
)

/**
* Get the verifiable credentials from the snap.
*/
const getVerifiableCredentials = useCallback(
async (params: SaveCredentialsRequestParams) => {
if (!connector) throw new TypeError('Connector is not defined')
async (params: SaveCredentialsRequestParams, _connector?: SnapConnector) => {
const currentConnector = _connector || connector

if (!currentConnector) throw new TypeError('Connector is not defined')

return connector.saveCredentials?.(params)
return currentConnector.saveCredentials?.(params)
},
[connector],
)

const createProof = useCallback(
async (params: CreateProofRequestParams) => {
if (!connector) throw new TypeError('Connector is not defined')
async (params: CreateProofRequestParams, _connector?: SnapConnector) => {
const currentConnector = _connector || connector

if (!currentConnector) throw new TypeError('Connector is not defined')

return connector.createProof(params)
return currentConnector.createProof(params)
},
[connector],
)

const getCredentials = useCallback(async () => {
if (!connector) throw new TypeError('Connector is not defined')
const getCredentials = useCallback(
async (_connector?: SnapConnector) => {
const currentConnector = _connector || connector

return await connector.getCredentials()
}, [connector])
if (!currentConnector) throw new TypeError('Connector is not defined')

return await currentConnector.getCredentials()
},
[connector],
)

const checkMetamaskExists = useCallback(async () => {
const _isMetamaskInstalled = await detectMetamaskInstalled()
Expand All @@ -154,6 +172,8 @@ export const MetamaskZkpSnapContextProvider: FC<HTMLAttributes<HTMLDivElement>>
const connector = await snap.getConnector()

setConnector(connector)

return connector
}, [])

const checkSnapStatus = useCallback(async () => {
Expand Down
55 changes: 33 additions & 22 deletions src/hooks/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ subscribe(web3Store, () => {
export const useAuth = () => {
const { jwt } = useAuthState()
const { init, provider } = useWeb3Context()
const { isSnapInstalled, connectOrInstallSnap, checkSnapStatus } = useMetamaskZkpSnapContext()
const { userDid, isSnapInstalled, connectOrInstallSnap, checkSnapStatus, createIdentity } =
useMetamaskZkpSnapContext()

const isJwtValid = useMemo(() => {
return !!jwt
Expand All @@ -26,51 +27,61 @@ export const useAuth = () => {
[isJwtValid, isSnapInstalled, provider?.isConnected],
)

const checkJwtValid = useCallback(() => {
// TODO: add jwt expiration check
return true
}, [])
const checkJwtValid = useCallback(
(did?: string) => {
const currentDid = did || userDid

const logOut = useCallback(async () => {
// TODO: add jwt expiration and did check
return !!currentDid
},
[userDid],
)

const logout = useCallback(async () => {
await provider?.disconnect()
await checkSnapStatus()
authStore.setJwt(jwt)
}, [checkSnapStatus, jwt, provider])

const connectProviders = useCallback(async () => {
await init(PROVIDERS.Metamask)
const connector = await connectOrInstallSnap()

await checkSnapStatus()

return createIdentity(connector)
}, [checkSnapStatus, connectOrInstallSnap, createIdentity, init])

const authorize = useCallback(
async (_jwt?: string) => {
const currentJwt = _jwt || jwt

if (!currentJwt) await logOut()
if (!currentJwt) await logout()

const isJwtValid = checkJwtValid()
const { identityIdString } = await connectProviders()

if (isJwtValid) {
authStore.setJwt(currentJwt)
return
}
const isJwtValid = checkJwtValid(identityIdString)

logOut()
if (!isJwtValid) {
logout()
}
},
[jwt, logOut, checkJwtValid],
[jwt, logout, connectProviders, checkJwtValid],
)

const login = useCallback(async () => {
await init(PROVIDERS.Metamask)
await connectOrInstallSnap()

await checkSnapStatus()
const { identityIdString } = await connectProviders()

// TODO: generateProof and /login
const jwt = 'mockJwt'
const jwt = identityIdString

await authorize(jwt)
}, [authorize, checkSnapStatus, connectOrInstallSnap, init])
authStore.setJwt(jwt)
}, [connectProviders])

return {
isAuthorized,
login,
authorize,
logOut,
logout,
}
}
3 changes: 1 addition & 2 deletions src/pages/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ export default function SignIn() {
await login()
} catch (error) {
ErrorHandler.process(error)
setIsPending(false)
}

setIsPending(false)
}, [login])

const installMMLink = useMemo(() => {
Expand Down
Loading

0 comments on commit 5ff2836

Please sign in to comment.