Display some error messages on the sign in page

Improves the UX by displaying some error messages on the sign in page
This commit is contained in:
Iain Collins
2020-07-27 03:11:43 +01:00
parent f6b7e0aad9
commit d0dbacfc4b
7 changed files with 73 additions and 59 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "next-auth",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"description": "Authentication for Next.js",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/iaincollins/next-auth.git",

View File

@@ -1,14 +1,18 @@
:root {
--color-background: #fff;
--color-primary: #444;
--color-control-border: #bbb;
--color-button-hover-background: #f9f9f9;
--color-button-active-background: #f5f5f5;
--color-button-active-background: #f9f9f9;
--color-button-active-border: #aaa;
--border-width: 1px;
--border-radius: .3rem;
--color-error: #c94b4b;
--color-info: #157efb;
--color-seperator: #ccc;
}
body {
background-color: var(--color-background);
margin: 0;
padding: 0;
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
@@ -39,7 +43,7 @@ input[type] {
width: 100%;
padding: .5rem 1rem;
border: var(--border-width) solid var(--color-control-border);
background: #fff;
background: var(--color-background);
font-size: 1rem;
border-radius: var(--border-radius);
box-shadow: inset 0 .1rem .2rem rgba(0,0,0,.2);
@@ -61,7 +65,7 @@ a.button {
line-height: 1rem;
&:link,
&:visited {
background-color: #fff;
background-color: var(--color-background);
color: var(--color-primary);
}
}
@@ -72,21 +76,20 @@ a.button {
padding: .75rem 1rem;
border: var(--border-width) solid var(--color-control-border);
color: var(--color-primary);
background-color: #fff;
background-color: var(--color-background);
font-size: 1rem;
border-radius: var(--border-radius);
transition: all .1s ease-in-out;
box-shadow: 0 0.15rem 0.3rem rgba(0,0,0,.15), inset 0 .1rem .2rem #fff, inset 0 -.1rem .1rem rgba(0,0,0,.05);
box-shadow: 0 0.15rem 0.3rem rgba(0,0,0,.15), inset 0 .1rem .2rem var(--color-background), inset 0 -.1rem .1rem rgba(0,0,0,.05);
font-weight: 500;
position: relative;
&:hover {
background-color: var(--color-button-hover-background);
cursor: pointer;
}
&:active {
box-shadow: 0 0.15rem 0.3rem rgba(0,0,0,.15), inset 0 .1rem .2rem #fff, inset 0 -.1rem .1rem rgba(0,0,0,.1);
box-shadow: 0 0.15rem 0.3rem rgba(0,0,0,.15), inset 0 .1rem .2rem var(--color-background), inset 0 -.1rem .1rem rgba(0,0,0,.1);
background-color: var(--color-button-active-background);
border-color: var(--color-button-active-border);
cursor: pointer;
@@ -143,19 +146,34 @@ a.site {
hr {
display: block;
border: 0;
border-top: 1px solid #ccc;
border-top: 1px solid var(--color-seperator);
margin: 1.5em auto 0 auto;
overflow: visible;
&::before {
content: "or";
background: #fff;
background: var(--color-background);
color: #888;
padding: 0 .4rem;
position: relative;
top: -.6rem;
}
}
.error {
background: #f5f5f5;
font-weight: 500;
border-radius: 0.3rem;
background: var(--color-info);
color: #fff;
p {
text-align: left;
padding: 0.5rem 1rem;
font-size: 0.9rem;
line-height: 1.2rem;
}
}
> div,
form {
display: block;

View File

@@ -235,12 +235,16 @@ export default async (req, res, userSuppliedOptions) => {
res.json({ csrfToken })
return done()
case 'signin':
if (options.pages.signIn) { return redirect(`${options.pages.signIn}${options.pages.signIn.includes('?') ? '&' : '?'}callbackUrl=${options.callbackUrl}`) }
if (options.pages.signIn) {
let redirectUrl = `${options.pages.signIn}${options.pages.signIn.includes('?') ? '&' : '?'}callbackUrl=${options.callbackUrl}`
if (req.query.error) { redirectUrl = `${redirectUrl}&error=${req.query.error}` }
return redirect(redirectUrl)
}
pages.render(req, res, 'signin', { baseUrl, basePath, providers: Object.values(options.providers), callbackUrl: options.callbackUrl, csrfToken }, done)
break
case 'signout':
if (options.pages.signOut) { return redirect(`${options.pages.signOut}${options.pages.signOut.includes('?') ? '&' : '?'}callbackUrl=${options.callbackUrl}`) }
if (options.pages.signOut) { return redirect(`${options.pages.signOut}${options.pages.signOut.includes('?') ? '&' : '?'}error=${error}`) }
pages.render(req, res, 'signout', { baseUrl, basePath, csrfToken, callbackUrl: options.callbackUrl }, done)
break

View File

@@ -2,7 +2,7 @@ import { h } from 'preact' // eslint-disable-line no-unused-vars
import render from 'preact-render-to-string'
export default ({ baseUrl, basePath, error, res }) => {
const signinPageUrl = `${baseUrl}${basePath}/signin` // @TODO Make sign in URL configurable
const signinPageUrl = `${baseUrl}${basePath}/signin`
let statusCode = 200
let heading = <h1>Error</h1>
@@ -15,51 +15,13 @@ export default ({ baseUrl, basePath, error, res }) => {
case 'OAuthCreateAccount':
case 'EmailCreateAccount':
case 'Callback':
heading = <h1>Sign in failed</h1>
message =
<div>
<div className='message'>
<p>Try signing with a different account.</p>
</div>
<p><a className='button' href={signinPageUrl}>Sign in</a></p>
</div>
break
case 'OAuthAccountNotLinked':
statusCode = 403
heading = <h1>Sign in failed</h1>
message =
<div>
<div className='message'>
<p>An account associated with your email address already exists.</p>
<p>Sign in with the same account you used originally to confirm your identity.</p>
</div>
<p><a className='button' href={signinPageUrl}>Sign in</a></p>
</div>
// @TODO Add this text when account linking is complete
// <p>Once you are signed in, you can link your accounts.</p>
// @TODO Display email sign in option if an email provider is configured
break
case 'EmailSignin':
heading = <h1>Sign in failed</h1>
message =
<div>
<div className='message'>
<p>Unable to send email.</p>
</div>
<p><a className='button' href={signinPageUrl}>Sign in</a></p>
</div>
break
case 'CredentialsSignin':
statusCode = 403
heading = <h1>Sign in failed</h1>
message =
<div>
<div className='message'>
<p>Check the details you provided are correct.</p>
</div>
<p><a className='button' href={signinPageUrl}>Sign in</a></p>
</div>
break
// These messages are displayed in line on the sign in page
res.status(302).setHeader('Location', `${signinPageUrl}?error=${error}`)
res.end()
return false
case 'Configuration':
statusCode = 500
heading = <h1>Server error</h1>

View File

@@ -18,6 +18,7 @@ function render (req, res, page, props, done) {
break
case 'error':
html = error({ ...props, res })
if (html === false) return done()
break
default:
html = error(props)

View File

@@ -2,7 +2,7 @@ import { h } from 'preact' // eslint-disable-line no-unused-vars
import render from 'preact-render-to-string'
export default ({ req, csrfToken, providers, callbackUrl }) => {
const { email } = req.query
const { email, error } = req.query
// We only want to render providers
const providersToRender = providers.filter(provider => {
@@ -18,8 +18,37 @@ export default ({ req, csrfToken, providers, callbackUrl }) => {
}
})
let errorMessage
if (error) {
switch (error) {
case 'Signin':
case 'OAuthSignin':
case 'OAuthCallback':
case 'OAuthCreateAccount':
case 'EmailCreateAccount':
case 'Callback':
errorMessage = <p>Try signing with a different account.</p>
break
case 'OAuthAccountNotLinked':
errorMessage = <p>To confirm your identity, sign in with the same account you used originally.</p>
break
case 'EmailSignin':
errorMessage = <p>Check your email address.</p>
break
case 'CredentialsSignin':
errorMessage = <p>Sign in failed. Check the details you provided are correct.</p>
break
default:
errorMessage = <p>Unable to sign in.</p>
break
}
}
return render(
<div className='signin'>
{errorMessage && <div className='error'>
{errorMessage}
</div>}
{providersToRender.map((provider, i) =>
<div key={provider.id} className='provider'>
{provider.type === 'oauth' &&

View File

@@ -17,7 +17,7 @@ html[data-theme="dark"] .button:active {
.button--primary,
.button--primary:hover{
background: linear-gradient(0deg, var(--ifm-color-primary-darkest) 0%, var(--ifm-color-primary-lighter) 100%) !important;
background: linear-gradient(0deg, #157efb 0%, var(--ifm-color-primary) 100%) !important;
color: #fff;
}
@@ -32,5 +32,5 @@ html[data-theme="dark"] .button:active {
html[data-theme="dark"] .button.button--secondary.button--outline {
background: linear-gradient(0deg, #000000 0%, #222222 100%) !important;
color: #eeee !important
color: #eee !important
}