( providers: readonly IProviderRow[], providerName: string, emailProviderKey: string )
| 182 | * disconnecting an OAuth provider deletes the link row outright. |
| 183 | */ |
| 184 | export const canDisconnect = ( |
| 185 | providers: readonly IProviderRow[], |
| 186 | providerName: string, |
| 187 | emailProviderKey: string |
| 188 | ): DisconnectDecision => { |
| 189 | if (providers.length === 0) { |
| 190 | return { ok: false, reason: NOT_FOUND_REASON }; |
| 191 | } |
| 192 | |
| 193 | const target = providers.find((row) => row.provider === providerName); |
| 194 | |
| 195 | if (target === undefined) { |
| 196 | return { ok: false, reason: NOT_FOUND_REASON }; |
| 197 | } |
| 198 | |
| 199 | const hasPassword = providers.some( |
| 200 | (row) => row.provider === emailProviderKey && row.passwordHash !== "" |
| 201 | ); |
| 202 | |
| 203 | const oauthCount = providers.filter( |
| 204 | (row) => row.provider !== emailProviderKey |
| 205 | ).length; |
| 206 | |
| 207 | if (providerName === emailProviderKey) { |
| 208 | if (oauthCount === 0) { |
| 209 | return { ok: false, reason: KEEP_ONE_METHOD_REASON }; |
| 210 | } |
| 211 | |
| 212 | return { ok: true, action: "clear-password" }; |
| 213 | } |
| 214 | |
| 215 | if (!hasPassword && oauthCount <= 1) { |
| 216 | return { ok: false, reason: KEEP_ONE_METHOD_REASON }; |
| 217 | } |
| 218 | |
| 219 | return { ok: true, action: "delete" }; |
| 220 | }; |
no outgoing calls
no test coverage detected