Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

fix: do not remove last identity provider #7739

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,8 @@ const ProfileMenu = ({ hideLogin, allowAvatarChange, isPopover, changeActiveMenu
(authState.linkedin && !oauthConnectedState.linkedin) ||
(authState.twitter && !oauthConnectedState.twitter)

const removeSocial =
(authState?.discord && oauthConnectedState.discord) ||
(authState.facebook && oauthConnectedState.facebook) ||
(authState.github && oauthConnectedState.github) ||
(authState.google && oauthConnectedState.google) ||
(authState.linkedin && oauthConnectedState.linkedin) ||
(authState.twitter && oauthConnectedState.twitter)
/**allow removing social logins if there is at least 1 social logins connected*/
const removeSocial = Object.values(oauthConnectedState).filter((value) => value).length >= 1

// const loadCredentialHandler = async () => {
// try {
Expand Down Expand Up @@ -557,51 +552,53 @@ const ProfileMenu = ({ hideLogin, allowAvatarChange, isPopover, changeActiveMenu
</div>

{!selfUser?.isGuest.value && removeSocial && (
<Text align="center" variant="body2" mb={1} mt={2}>
{t('user:usermenu.profile.removeSocial')}
</Text>
)}
<>
<Text align="center" variant="body2" mb={1} mt={2}>
{t('user:usermenu.profile.removeSocial')}
</Text>

<div className={styles.socialContainer}>
{authState?.discord && oauthConnectedState.discord && (
<IconButton
id="discord"
icon={<DiscordIcon viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.google && oauthConnectedState.google && (
<IconButton
id="google"
icon={<GoogleIcon viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.facebook && oauthConnectedState.facebook && (
<IconButton
id="facebook"
icon={<Icon type="Facebook" viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.linkedin && oauthConnectedState.linkedin && (
<IconButton
id="linkedin"
icon={<LinkedInIcon viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.twitter && oauthConnectedState.twitter && (
<IconButton
id="twitter"
icon={<Icon type="Twitter" viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.github && oauthConnectedState.github && (
<IconButton id="github" icon={<Icon type="GitHub" />} onClick={handleRemoveOAuthServiceClick} />
)}
</div>
<div className={styles.socialContainer}>
{authState?.discord && oauthConnectedState.discord && (
<IconButton
id="discord"
icon={<DiscordIcon viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.google && oauthConnectedState.google && (
<IconButton
id="google"
icon={<GoogleIcon viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.facebook && oauthConnectedState.facebook && (
<IconButton
id="facebook"
icon={<Icon type="Facebook" viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.linkedin && oauthConnectedState.linkedin && (
<IconButton
id="linkedin"
icon={<LinkedInIcon viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.twitter && oauthConnectedState.twitter && (
<IconButton
id="twitter"
icon={<Icon type="Twitter" viewBox="0 0 40 40" />}
onClick={handleRemoveOAuthServiceClick}
/>
)}
{authState?.github && oauthConnectedState.github && (
<IconButton id="github" icon={<Icon type="GitHub" />} onClick={handleRemoveOAuthServiceClick} />
)}
</div>
</>
)}
</>
)}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NotFound } from '@feathersjs/errors'
import { MethodNotAllowed, NotFound } from '@feathersjs/errors'
import { HookContext } from '@feathersjs/feathers'
import { iff, isProvider } from 'feathers-hooks-common'

Expand Down Expand Up @@ -44,6 +44,26 @@ const checkIdentityProvider = (): any => {
}
}

const checkOnlyIdentityProvider = () => {
barankyle marked this conversation as resolved.
Show resolved Hide resolved
return async (context: HookContext): Promise<HookContext> => {
if (!context.id) {
// If trying to delete multiple providers, do not check if only 1 identity provider exists
return context
}

const thisIdentityProvider = await (context.app.service('identity-provider') as any).Model.findByPk(context.id)

const providers = await context.app
.service('identity-provider')
.find({ query: { userId: thisIdentityProvider.userId } })

if (providers.total <= 1) {
throw new MethodNotAllowed('Cannot remove the only provider')
}
return context
}
}

export default {
before: {
all: [],
Expand All @@ -52,7 +72,7 @@ export default {
create: [],
update: [iff(isProvider('external'), authenticate() as any, checkIdentityProvider())],
patch: [iff(isProvider('external'), authenticate() as any, checkIdentityProvider())],
remove: [iff(isProvider('external'), authenticate() as any, checkIdentityProvider())]
remove: [iff(isProvider('external'), authenticate() as any, checkIdentityProvider()), checkOnlyIdentityProvider()]
},
after: {
all: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import assert from 'assert'
import { v1 } from 'uuid'

import { IdentityProviderInterface } from '@etherealengine/common/src/dbmodels/IdentityProvider'
import { destroyEngine } from '@etherealengine/engine/src/ecs/classes/Engine'

import { Application } from '../../../declarations'
import { createFeathersExpressApp } from '../../createApp'

let providers: any = []
let userId: string

describe('identity-provider service', () => {
let app: Application
let providers: IdentityProviderInterface[] = []

before(async () => {
app = createFeathersExpressApp()
await app.setup()
Expand All @@ -35,8 +38,11 @@ describe('identity-provider service', () => {
},
{}
)

providers.push(item)

userId = item.userId

assert.equal(item.type, type)
assert.equal(item.token, token)
assert.ok(item.userId)
Expand All @@ -49,10 +55,12 @@ describe('identity-provider service', () => {
const item = await app.service('identity-provider').create(
{
type,
token
token,
userId
},
{}
)

providers.push(item)

assert.equal(item.type, type)
Expand All @@ -69,10 +77,12 @@ describe('identity-provider service', () => {
{
type,
token,
password
password,
userId
},
{}
)

providers.push(item)

assert.equal(item.type, type)
Expand All @@ -81,25 +91,60 @@ describe('identity-provider service', () => {
})

it('should find identity providers', async () => {
for (const provider of providers) {
const item = await app.service('identity-provider').find({
query: {
userId: provider.userId
}
})

assert.ok(item, 'Identity provider item is found')
}
const item = await app.service('identity-provider').find({
query: {
userId
}
})

assert.ok(item, 'Identity provider item is found')
barankyle marked this conversation as resolved.
Show resolved Hide resolved
assert.equal((item as any).total, providers.length)
})

it('should remove identity providers', async () => {
for (const provider of providers) {
const item = await app.service('identity-provider').remove(null, {
query: {
userId: provider.userId
}
})
assert.ok(item, 'Identity provider item is removed')
}
it('should remove an identity provider by id', async () => {
await app.service('identity-provider').remove(providers[0].id)

const item = await app.service('identity-provider').find({
query: {
id: providers[0].id
}
})

assert.equal((item as any).total, 0)
})

it('should remove identity providers by user id', async () => {
await app.service('identity-provider').remove(null, {
query: {
userId
}
})

const item = await app.service('identity-provider').find({
query: {
userId
}
})

assert.equal((item as any).total, 0)
})

it('should not be able to remove the only identity provider', async () => {
const type = 'guest'
const token = v1()

const item = await app.service('identity-provider').create(
{
type,
token
},
{}
)

assert.rejects(() => app.service('identity-provider').remove(item.id), {
name: 'MethodNotAllowed'
})

assert.ok(true)
})
})