This repository has been archived by the owner on Apr 10, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: UI Auth/Login #41
Merged
Merged
Changes from 29 commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
a10c20b
chore: rename hooks; cleanup
markphelps df414ae
chore: remove auth provider
markphelps de1deb3
Merge branch 'main' into hooks-cleanup
markphelps f388093
chore: rename hooks back
markphelps 0a78af8
Merge branch 'hooks-cleanup' of https://github.com/flipt-io/flipt-ui …
markphelps da6bb65
chore: forgot to stage renamed files
markphelps 032b24d
feat(auth): wip
markphelps 3341896
Merge branch 'main' into login
markphelps bbaf690
chore: add include creds everywhere; create delete helper func
markphelps 2e7fdd4
chore: wip login
markphelps 2081096
chore: wip
markphelps 35d9b79
Merge branch 'main' into login
markphelps 686e4b0
chore: wip
markphelps 28ec7bd
chore: wip fix auth
markphelps b1850a5
chore: load self data
markphelps cd11338
feat: load user profile
markphelps 172d858
chore: fix login redirect when auth not required
markphelps fbc0081
chore: rename session, fix exhaustive deps issue
markphelps e78804b
fix: api calls
markphelps 04a7c50
chore: show name on hover
markphelps fb8ce1d
chore: get images working in dev mode; opacity on user ring
markphelps 559baf4
feat: implement logout
markphelps e2fe2ef
feat: add known providers/icons
markphelps 158d53b
Merge branch 'main' into login
markphelps 588943f
chore: set session null on logout
markphelps c29777d
Merge branch 'login' of https://github.com/flipt-io/flipt-ui into login
markphelps 494c7b7
chore: rm credentials include
markphelps c539acf
Merge branch 'main' into login
markphelps d117a98
fix: linter warning
markphelps b2b3ed5
fix: loading of providers
markphelps 6a15387
fix: login page
markphelps e153987
fix: session infinite loop
markphelps baf347c
feat(api): add CSRF token support
GeorgeMac 10d07bc
fix: show empty user if no imageURL
markphelps 225b9eb
fix: set headers
markphelps 4aa0794
fix: try to fix these loadeffect loops
markphelps 277156c
chore: disable refresh interval in swr
markphelps 9155a09
fix(csrf): drop type constraints on setCsrf
GeorgeMac 137d98d
chore: make login dynamic import
markphelps File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { | ||
faGitlab, | ||
faGoogle, | ||
faOpenid | ||
} from '@fortawesome/free-brands-svg-icons'; | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
import { toLower, upperFirst } from 'lodash'; | ||
import { useCallback, useEffect, useState } from 'react'; | ||
import { Navigate } from 'react-router-dom'; | ||
import logoFlag from '~/assets/logo-flag.png'; | ||
import { listAuthMethods } from '~/data/api'; | ||
import { useError } from '~/data/hooks/error'; | ||
import { useSession } from '~/data/hooks/session'; | ||
import { AuthMethod, AuthMethodOIDC } from '~/types/Auth'; | ||
|
||
interface ILoginProvider { | ||
displayName: string; | ||
icon?: any; | ||
} | ||
|
||
const knownProviders: Record<string, ILoginProvider> = { | ||
google: { | ||
displayName: 'Google', | ||
icon: faGoogle | ||
}, | ||
gitlab: { | ||
displayName: 'GitLab', | ||
icon: faGitlab | ||
}, | ||
auth0: { | ||
displayName: 'Auth0' | ||
} | ||
}; | ||
|
||
export default function Login() { | ||
const { session } = useSession(); | ||
|
||
const [providers, setProviders] = useState< | ||
{ | ||
name: string; | ||
authorize_url: string; | ||
callback_url: string; | ||
icon: any; | ||
}[] | ||
>([]); | ||
|
||
const { setError, clearError } = useError(); | ||
|
||
const authorize = async (uri: string) => { | ||
const res = await fetch(uri, { | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
} | ||
}); | ||
|
||
if (!res.ok || res.status !== 200) { | ||
setError(new Error('Unable to authenticate: ' + res.text())); | ||
return; | ||
} | ||
|
||
clearError(); | ||
const body = await res.json(); | ||
window.location.href = body.authorizeUrl; | ||
}; | ||
|
||
const loadAvailableProviders = useCallback(async () => { | ||
try { | ||
const resp = await listAuthMethods(); | ||
// TODO: support alternative auth methods | ||
const authOIDC = resp.methods.find( | ||
(m: AuthMethod) => m.method === 'METHOD_OIDC' && m.enabled | ||
) as AuthMethodOIDC; | ||
|
||
if (!authOIDC) { | ||
return; | ||
} | ||
|
||
const loginProviders = Object.entries(authOIDC.metadata.providers).map( | ||
([k, v]) => { | ||
k = toLower(k); | ||
return { | ||
name: knownProviders[k]?.displayName || upperFirst(k), // if we dont know the provider, just capitalize the first letter | ||
authorize_url: v.authorize_url, | ||
callback_url: v.callback_url, | ||
icon: knownProviders[k]?.icon || faOpenid // if we dont know the provider icon, use the openid icon | ||
}; | ||
} | ||
); | ||
setProviders(loginProviders); | ||
} catch (err) { | ||
setError(err instanceof Error ? err : Error(String(err))); | ||
} | ||
}, [setError]); | ||
|
||
useEffect(() => { | ||
loadAvailableProviders(); | ||
}, [loadAvailableProviders]); | ||
|
||
if (session) { | ||
return <Navigate to="/" />; | ||
} | ||
|
||
return ( | ||
<> | ||
<div className="flex min-h-screen flex-col justify-center sm:px-6 lg:px-8"> | ||
<main className="flex px-6 py-10"> | ||
<div className="w-full overflow-x-auto px-4 sm:px-6 lg:px-8"> | ||
<div className="sm:mx-auto sm:w-full sm:max-w-md"> | ||
<img | ||
src={logoFlag} | ||
alt="logo" | ||
width={512} | ||
height={512} | ||
className="m-auto h-20 w-auto" | ||
/> | ||
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900"> | ||
Login to Flipt | ||
</h2> | ||
</div> | ||
|
||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-sm"> | ||
<div className="py-8 px-4 sm:px-10"> | ||
<div className="mt-6 flex flex-col space-y-5"> | ||
{providers.map((provider) => ( | ||
<div key={provider.name}> | ||
<a | ||
href="#" | ||
className="inline-flex w-full justify-center rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:text-violet-500 hover:shadow-violet-300" | ||
onClick={(e) => { | ||
e.preventDefault(); | ||
authorize(provider.authorize_url); | ||
}} | ||
> | ||
<span className="sr-only"> | ||
Sign in with {provider.name} | ||
</span> | ||
<FontAwesomeIcon | ||
icon={provider.icon} | ||
className="text-gray h-5 w-5" | ||
aria-hidden={true} | ||
/> | ||
<span className="ml-2">With {provider.name}</span> | ||
</a> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</main> | ||
</div> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it would cause an endless loop of loading providers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i checked with console.log it only calls once, I'm wondering if its because its wrapped in a
useCallback
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be because you're passing the
loadAvailableProviders
function as an arg below?useEffect
docs suggest the second argument is compared between each render.If it doesn't change then the first argument function is not called again.
Seems to me that function will never change. Or maybe there is some overloaded alternative behaviour when a function is passed as second arg.
Seems you could also pass
[]
for the same effect:https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like more often than not you want state in your dependencies array passed to
useEffect
.There are cases where you might pass a function. Though that seems to be when they're defined out of scope:
https://reacttraining.com/blog/when-to-use-functions-in-hooks-dependency-array/
(I admit as I kept reading this I started to glaze over and maybe missed something key)