Merged PR 849: Passwordless auth poc

Related work items: #7608
This commit is contained in:
Robert Bond
2022-05-06 09:12:43 +00:00
26 changed files with 4745 additions and 2567 deletions
+131
View File
@@ -0,0 +1,131 @@
ACCESS_TOKEN_ENDPOINT = https://login.microsoftonline.com/
//ESNR Tenant
TENANT = a8d860de-d5e1-45af-8376-d93f02e6b502
//WGO Tenant
//TENANT = 9ac1a5ab-cd58-409e-a1b6-6b10422a68f5
GRANT_TYPE = "client_credentials"
HASHKEY = 028ffbae928d7191c0017f298260995f901d78eabde1436c0af0423459cc3715832136d84f68818d3a96399467b52143e273d4f3bf4ae190848891535421cd6c
NEXT_PUBLIC_HASHKEY = 028ffbae928d7191c0017f298260995f901d78eabde1436c0af0423459cc3715832136d84f68818d3a96399467b52143e273d4f3bf4ae190848891535421cd6c
//Dev oauth ESNR
CLIENT_ID = e082def0-3453-461f-9a5e-f4ab127dc015
CLIENT_SECRET = qW.7Q~TSr3sZmLEnJQJPXEdOckb_yo2t1yJmY
RELAY_ROOT = https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/
RELAYURI = "dev-pedw-ns.servicebus.windows.net"
RELAYPATH = "dev-pedw-hc"
//Test Oauth ESNR
//CLIENT_ID = 6619ad85-c908-490d-811b-de8c93c0f2aa
//CLIENT_SECRET = qKo7Q~5QvLEplbwnBbint_I5lXqx_-kDm8Vj-
//RELAY_ROOT = https://test-pedw-ns.servicebus.windows.net/test-pedw-hc/
//RELAYURI = "test-pedw-ns.servicebus.windows.net"
//RELAYPATH = "test-pedw-hc"
//Preprod Oauth WGO
//CLIENT_ID = ab6c4678-b31a-4eb3-b429-e039120e488a
//CLIENT_SECRET = Ioo7Q~RJfhWsE45wPsuHm1CzRk1SppnNN3lZF
//RELAY_ROOT = https://pp-pedw-ns.servicebus.windows.net/pp-pedw-hc/
//RELAYURI = "pp-pedw-ns.servicebus.windows.net"
//RELAYPATH = "pp-pedw-hc"
//Prod Oauth WGO
//CLIENT_ID = 3e4141a3-1fbd-454e-98f0-7f3a4f14a3cc
//CLIENT_SECRET = mJN7Q~lFu.xFGa5KCc.zwIm_fJAWxajyQaFo~
//RELAY_ROOT = https://prod-pedw-ns.servicebus.windows.net/prod-pedw-hc/
//RELAYURI = "prod-pedw-ns.servicebus.windows.net"
//RELAYPATH = "prod-pedw-hc"
GOOGLE_TAG_MANAGER = GTM-T78CBC3
SHOWLOGIN = "true"
SHOWREPRESENTATIONS = false
SHOWFILEUPLOAD = "false"
SHOWMAPS = "true"
//BASIC_AUTH_CREDENTIALS = pinswg:password|pinswg2:password2
PORT= "3000"
DEFAULT_DOMAIN = "localhost"
I18N_DOMAIN = "cymru.local"
//I18N_DOMAIN ="dev-gwaithcynllunio.gwasanaeth.llyw.cymru"
//I18N_DOMAIN ="stage-gwaithcynllunio.gwasanaeth.llyw.cymru"
//I18N_DOMAIN ="pp-gwaithcynllunio.gwasanaeth.llyw.cymru"s
//I18N_DOMAIN ="gwaithcynllunio.gwasanaeth.llyw.cymru"
//gov gateway
GG_PROVIDER_CONFIG_ENDPOINT = "https://api.ete.access.service.gov.uk/.well-known/openid-configuration"
GG_CLIENT_ID = 5vTukeSpN7d6uBV7YLb7A5wOctzzkZ
GG_CLIENT_SECRET = z1PGLAKPxIhjMBegTdzX22AayIORc1GsCbMQZ6mieBn7zChjzzxup5lkIByEmsYS
//GG_REDIRECT_URI = "http://localhost:3000"
GG_REDIRECT_URI = "https://dev-planningcasework.service.gov.wales"
NEXT_PUBLIC_GG_CLIENT_ID = 5vTukeSpN7d6uBV7YLb7A5wOctzzkZ
NEXT_PUBLIC_GG_REDIRECT_URI = "http://localhost:3000"
API_ROOT = "http://localhost:3000"
GOOGLE_MAP_API_KEY = AIzaSyCzeVrHt544bp_72YCFkw21eDsmI2JEsQo
//EMAIL_SERVER_HOST=smtp.livemail.co.uk
//EMAIL_SERVER_PORT=465
//EMAIL_SERVER_USER=rob@rdbsolutions.co.uk
//EMAIL_SERVER_PASSWORD=R0b3rt72
//EMAIL_FROM=rob@rdbsolutions.co.uk
EMAIL_SERVER_HOST=smtp.sendgrid.net
EMAIL_SERVER_PORT=465
EMAIL_SERVER_USER=apikey
EMAIL_SERVER_PASSWORD=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
EMAIL_FROM=rob@rdbsolutions.co.uk
NEXT_PUBLIC_MAGIC_PK=pk_live_674E13244D1BB0A4
MAGIC_SK=sk_live_F0E74A9557051A65
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET= # Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32
APPLE_ID=
APPLE_TEAM_ID=
APPLE_PRIVATE_KEY=
APPLE_KEY_ID=
AUTH0_ID=
AUTH0_SECRET=
AUTH0_ISSUER=
FACEBOOK_ID=
FACEBOOK_SECRET=
GITHUB_ID=
GITHUB_SECRET=
GOOGLE_ID=
GOOGLE_SECRET=
TWITTER_ID=
TWITTER_SECRET=
//EMAIL_SERVER=smtp://username:password@smtp.example.com:587
//EMAIL_FROM=NextAuth <noreply@example.com>
//DATABASE_URL=sqlite://localhost/:memory:?synchronize=true
DATABASE_URL=mysql://nextAuthAdmin:!Sky9fish1972@77.68.17.157:3306/nextauthDB?synchronise=true
SECRET=SuperSecret
+15 -1
View File
@@ -74,4 +74,18 @@ NEXT_PUBLIC_GG_REDIRECT_URI = "http://localhost:3000"
API_ROOT = "http://localhost:3000"
GOOGLE_MAP_API_KEY = AIzaSyCzeVrHt544bp_72YCFkw21eDsmI2JEsQo
GOOGLE_MAP_API_KEY = AIzaSyCzeVrHt544bp_72YCFkw21eDsmI2JEsQo
//Next Auth vars
EMAIL_SERVER_HOST=smtp.sendgrid.net
EMAIL_SERVER_PORT=465
EMAIL_SERVER_USER=apikey
EMAIL_SERVER_PASSWORD=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
EMAIL_FROM=rob@rdbsolutions.co.uk
NEXTAUTH_URL=http://localhost:3000
DATABASE_URL=mysql://nextAuthAdmin:!Sky9fish1972@77.68.17.157:3306/nextauthDB?synchronise=true
SECRET=SuperSecret
+2 -2
View File
@@ -195,7 +195,7 @@ const RegisterForm = (props) => {
)}
/>
<Field
{/* <Field
name="pinswg_custom_password"
id="pinswg_custom_password"
component={RenderTextfield}
@@ -226,7 +226,7 @@ const RegisterForm = (props) => {
errorMsg={t(
"account:register-new-account-confirm-password-label-errormsg"
)}
/>
/> */}
</fieldset>
<fieldset className="govuk-fieldset">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
+18
View File
@@ -741,6 +741,24 @@ const Breadcrumbs = (props) => {
) : (
""
)}
{router.pathname == "/auth/signin" ? (
<>
<li className="govuk-breadcrumbs__list-item">
Sign in
</li>
</>
) : (
""
)}
{router.pathname == "/auth/verify-request" ? (
<>
<li className="govuk-breadcrumbs__list-item">
Check your email
</li>
</>
) : (
""
)}
{/* {!_.isEmpty(slug) ? (
<>
<li className="govuk-breadcrumbs__list-item">
+19 -5
View File
@@ -27,6 +27,7 @@ import RepAgent from "./representationAgent";
import RepInterestedPartyPerson from "./representationInterestedPartyPerson";
import RepLandOwner from "./representationLandowner";
import RepCompleteSubmit from "./representationCompleteSubmit";
import { useSession, signIn, signOut } from "next-auth/react";
import {
RenderPickList,
@@ -39,6 +40,7 @@ let MakeRepresentation = (props) => {
const { acceptedFiles, getRootProps, getInputProps } = useDropzone();
const [clearRepForm, setClearRepForm] = useState(false);
const { data: session } = useSession();
const router = useRouter();
const { locale } = router;
@@ -250,11 +252,23 @@ let MakeRepresentation = (props) => {
/>
) : (
<>
<h1 className="govuk-heading-l">
Make a representation on:{" "}
{currentView.caseReference.currentReference}
</h1>
{whichControl()}
{!session ? (
<button
className="govuk-button withChevron govuk-button-cta"
type="button"
onClick={() => signIn("email")}
>
Sign in to raise representation
</button>
) : (
<>
<h1 className="govuk-heading-l">
Make a representation on:{" "}
{currentView.caseReference.currentReference}
</h1>
{whichControl()}
</>
)}
</>
)}
</form>
+1 -1
View File
@@ -65,7 +65,7 @@ const CaseSummary = (props) => {
} = props;
const noRepresentationLink = [
"/case",
//"/case",
"/dnsdetails",
"/dns/[developmentName]",
];
+8 -1
View File
@@ -7,6 +7,9 @@ import { useDispatch, useSelector, shallowEqual, connect } from "react-redux";
import { setLogout } from "../store/accountDetails/action";
import _ from "lodash";
import { parseCookies, setCookie, destroyCookie } from "nookies";
import { useSession, signIn, signOut } from "next-auth/react";
const Header = (props) => {
let { t, lang } = useTranslation();
@@ -33,6 +36,9 @@ const Header = (props) => {
"/myportal/error",
"/accessibility",
"/details-about-cookies",
"/auth/verify-request",
"/auth/signin",
"/auth/error",
];
const hasContactLink = [
"/dns/application-process",
@@ -214,7 +220,8 @@ const Header = (props) => {
<a
onClick={() => {
destroyCookie({}, "pinsUser"),
window.localStorage.clear();
window.localStorage.clear(),
signOut({ callbackUrl: "/logout" });
}}
className="govuk-header__link govuk-!-margin-right-3"
>
+198 -147
View File
@@ -9,6 +9,8 @@ import { getLogin } from "../../actions";
import { setLoggedInUserId } from "../../store/accountDetails/action";
import { parseCookies, setCookie, destroyCookie } from "nookies";
import { useSession, signIn, signOut } from "next-auth/react";
const required = (errorMsg) => (value) =>
value || typeof value === "number" ? undefined : errorMsg;
@@ -118,6 +120,8 @@ let Login = (props) => {
GG_REDIRECT_URI,
} = props;
const { data: session } = useSession();
const router = useRouter();
const { locale } = router;
@@ -201,163 +205,210 @@ let Login = (props) => {
router.replace(ggUrl);
};
if (session === "loading") {
return <h1>loading...</h1>;
}
if (session) {
<>
<div>Signed in as {session.user?.email}</div>
<br />
<button type="button" onClick={() => signOut()}>
Signout
</button>
</>;
}
return (
<div className="card" id="login-card">
<div className="card-body active">
<h3 className="heading-small card-heading">
{t("home:login-card-title")}
</h3>{" "}
<form onSubmit={handleSubmit(onHandleSubmit)}>
{validLoginID == false && (
<div className="govuk-error-message govuk-form-group--error">
Incorrect username or password
<>
<div className="card" id="login-card">
<div className="card-body active">
<h3 className="heading-small card-heading">
{t("home:login-card-title")}
</h3>{" "}
<div className="govuk-form-group govuk-!-margin-top-4">
<div className="govuk-body">
{session && "Signed in as " + session.user?.email}
</div>
)}
<Field
// validate={[
// required(
// t("home:casesearch-search-validation-required")
// ),
// minLength(
// t("home:casesearch-search-validation-minlength")
// ),
// ]}
className="govuk-input"
component={RenderTextfield}
id="loginUserID"
name="loginUserID"
type="text"
aria-describedby="basicSearch-hint"
hint1={t("home:login-card-id-label")}
label={t("home:login-card-id-hint")}
/>
<Field
// validate={[
// required(
// t("home:casesearch-search-validation-required")
// ),
// minLength(
// t("home:casesearch-search-validation-minlength")
// ),
// ]}
className="govuk-input"
component={RenderPasswordfield}
id="loginUserPassword"
name="loginUserPassword"
type="password"
label="Password"
/>
<div className="govuk-form-group">
<button type="submit" className="govuk-button">
{t("home:login-card-button")}
</button>
<br />
{!session && (
<button
className="govuk-button withChevron govuk-button-cta"
type="button"
onClick={() => signIn("email")}
>
Sign in with email address
</button>
)}
{session && (
<button
className="govuk-button"
type="button"
onClick={() => signOut()}
>
Magic Link Sign Out
</button>
)}
</div>
</form>
<div className="govuk-form-group govuk-!-margin-top-4">
<p className="govuk-body-xs">
<a
onClick={() => {
govGatwayLoginRedirect();
}}
className="govuk-button withChevron govuk-button-cta"
>
{t("home:login-via-gov-gateway-label")}
</a>
</p>
<p className="govuk-body-s">
{t("home:login-card-register-label")}{" "}
<a
href={
router.locale == "cy"
? "https://www.ete.access.service.gov.uk/login/create-account?lang=cy"
: "https://www.ete.access.service.gov.uk/login/create-account"
}
className="govuk-link--no-underline"
>
{t("home:login-card-register-link")}
</a>
</p>
<p className="govuk-body-s">
<a
onClick={() => {
govGatwayForgottenLink();
}}
className="govuk-link--no-underline"
>
{t("home:login-card-forgotten-label")}{" "}
</a>
</p>
<hr className="govuk-!-margin-bottom-4" />
<p className="govuk-body-s">
{t("home:login-comingsoon-temp-2")}
</p>
<ul>
<li>
<a
className="govuk-link--no-underline"
target="_blank"
href={t("home:login-comingsoon-forms-link-1")}
rel="noreferrer"
>
{t("home:login-comingsoon-forms-label-1")}
</a>
</li>
<li>
{" "}
<a
className="govuk-link--no-underline"
target="_blank"
href={t("home:login-comingsoon-forms-link-2")}
rel="noreferrer"
>
{t("home:login-comingsoon-forms-label-2")}
</a>
</li>
<li>
{" "}
<a
className="govuk-link--no-underline"
target="_blank"
href={t("home:login-comingsoon-forms-link-3")}
rel="noreferrer"
>
{t("home:login-comingsoon-forms-label-3")}
</a>
</li>
<li>
{" "}
<a
className="govuk-link--no-underline"
target="_blank"
href={t("home:login-comingsoon-forms-link-4")}
rel="noreferrer"
>
{t("home:login-comingsoon-forms-label-4")}
</a>
</li>
</ul>
<br />
<form onSubmit={handleSubmit(onHandleSubmit)}>
{validLoginID == false && (
<div className="govuk-error-message govuk-form-group--error">
Incorrect username or password
</div>
)}
<Field
// validate={[
// required(
// t("home:casesearch-search-validation-required")
// ),
// minLength(
// t("home:casesearch-search-validation-minlength")
// ),
// ]}
className="govuk-input"
component={RenderTextfield}
id="loginUserID"
name="loginUserID"
type="text"
aria-describedby="basicSearch-hint"
hint1={t("home:login-card-id-label")}
label={t("home:login-card-id-hint")}
/>
<p className="govuk-body-s">
{t("home:login-comingsoon-email-link-label")}{" "}
<a
href={"mailto:" + t("home:login-contact-email")}
className="govuk-link--no-underline"
>
{t("home:login-contact-email")}
</a>
</p>
<Field
// validate={[
// required(
// t("home:casesearch-search-validation-required")
// ),
// minLength(
// t("home:casesearch-search-validation-minlength")
// ),
// ]}
className="govuk-input"
component={RenderPasswordfield}
id="loginUserPassword"
name="loginUserPassword"
type="password"
label="Password"
/>
<div className="govuk-form-group">
<button type="submit" className="govuk-button">
{t("home:login-card-button")}
</button>
</div>
</form>
{/* <div className="govuk-form-group govuk-!-margin-top-4">
<p className="govuk-body-xs">
<a
onClick={() => {
govGatwayLoginRedirect();
}}
className="govuk-button withChevron govuk-button-cta"
>
{t("home:login-via-gov-gateway-label")}
</a>
</p>
<p className="govuk-body-s">
{t("home:login-card-register-label")}{" "}
<a
href={
router.locale == "cy"
? "https://www.ete.access.service.gov.uk/login/create-account?lang=cy"
: "https://www.ete.access.service.gov.uk/login/create-account"
}
className="govuk-link--no-underline"
>
{t("home:login-card-register-link")}
</a>
</p>
<p className="govuk-body-s">
<a
onClick={() => {
govGatwayForgottenLink();
}}
className="govuk-link--no-underline"
>
{t("home:login-card-forgotten-label")}{" "}
</a>
</p>
<hr className="govuk-!-margin-bottom-4" />
<p className="govuk-body-s">
{t("home:login-comingsoon-temp-2")}
</p>
<ul>
<li>
<a
className="govuk-link--no-underline"
target="_blank"
href={t(
"home:login-comingsoon-forms-link-1"
)}
rel="noreferrer"
>
{t("home:login-comingsoon-forms-label-1")}
</a>
</li>
<li>
{" "}
<a
className="govuk-link--no-underline"
target="_blank"
href={t(
"home:login-comingsoon-forms-link-2"
)}
rel="noreferrer"
>
{t("home:login-comingsoon-forms-label-2")}
</a>
</li>
<li>
{" "}
<a
className="govuk-link--no-underline"
target="_blank"
href={t(
"home:login-comingsoon-forms-link-3"
)}
rel="noreferrer"
>
{t("home:login-comingsoon-forms-label-3")}
</a>
</li>
<li>
{" "}
<a
className="govuk-link--no-underline"
target="_blank"
href={t(
"home:login-comingsoon-forms-link-4"
)}
rel="noreferrer"
>
{t("home:login-comingsoon-forms-label-4")}
</a>
</li>
</ul>
<br />
<p className="govuk-body-s">
{t("home:login-comingsoon-email-link-label")}{" "}
<a
href={"mailto:" + t("home:login-contact-email")}
className="govuk-link--no-underline"
>
{t("home:login-contact-email")}
</a>
</p>
</div> */}
</div>
</div>
{/* <LoadingOverlay
{/* <LoadingOverlay
active={showSpinnerState}
spinner
text="Logging in"
/> */}
</div>
</div>
</>
);
};
+2 -1
View File
@@ -12,7 +12,7 @@ import Dns from "./homepage/dns";
const MainHome = (props) => {
let { t } = useTranslation();
const { showLogin, GG_REDIRECT_URI, GG_CLIENT_ID } = props;
const { showLogin, GG_REDIRECT_URI, GG_CLIENT_ID, session } = props;
const router = useRouter();
const { locale } = router;
@@ -33,6 +33,7 @@ const MainHome = (props) => {
<Login
GG_CLIENT_ID={GG_CLIENT_ID}
GG_REDIRECT_URI={GG_REDIRECT_URI}
session={session}
/>
)}
{/* <Inspectorate /> */}
+4 -1
View File
@@ -19,6 +19,8 @@ import { useState } from "react";
import TimeOut from "../components/timeout";
import UploadFile from "./myportal/uploadFile";
import { useSession, signIn, signOut } from "next-auth/react";
const MyPortal = (props) => {
const { showFileUpload } = props;
let { t } = useTranslation();
@@ -57,7 +59,8 @@ const MyPortal = (props) => {
console.log("////////////", "\n", "logout", "\n", "//////////");
destroyCookie({}, "pinsUser"),
window.localStorage.clear(),
router.replace(props.ggLogout);
//router.replace(props.ggLogout);
signOut({ callbackUrl: "/logout" });
};
return (
+2 -2
View File
@@ -27,7 +27,7 @@ export default function YourAccount() {
</div>
</div>
<div className="card-body">
{/* <div className="card-body">
<h3 className="heading-small card-heading">
{t("myportal:changepassword-card-title")}
</h3>
@@ -43,7 +43,7 @@ export default function YourAccount() {
</a>
</Link>
</div>
</div>
</div> */}
</div>
</>
);
+2 -2
View File
@@ -4,6 +4,7 @@ import { destroyCookie } from "nookies";
import { useState } from "react";
import IdleTimer from "react-idle-timer";
import TimeoutModal from "./timeoutmodal";
import { useSession, signIn, signOut } from "next-auth/react";
const TimeOut = (props) => {
let { t } = useTranslation();
@@ -41,8 +42,7 @@ const TimeOut = (props) => {
console.log("////////////", "\n", "logout", "\n", "//////////");
destroyCookie({}, "pinsUser"),
window.localStorage.clear(),
//router.replace(props.ggLogout);
router.replace("/logout");
signOut({ callbackUrl: "/logout" });
};
return (
+10 -1
View File
@@ -10,6 +10,9 @@
},
"dependencies": {
"@googlemaps/react-wrapper": "^1.1.24",
"@magic-sdk/admin": "^1.4.0",
"@next-auth/prisma-adapter": "^1.0.3",
"@prisma/client": "^3.12.0",
"@webdeb/next-styles": "^1.1.1",
"applicationinsights": "^2.2.0",
"axios": "^0.24.0",
@@ -31,14 +34,18 @@
"leaflet": "^1.7.1",
"leaflet-defaulticon-compatibility": "^0.1.1",
"lodash": "^4.17.21",
"magic-sdk": "^8.1.0",
"moment": "^2.29.1",
"multer": "^1.4.4",
"mysql": "^2.18.1",
"nanoid": "^3.2.0",
"next": "^12.0.8",
"next-auth": "^4.3.1",
"next-connect": "^0.12.2",
"next-redux-wrapper": "^7.0.5",
"next-translate": "^1.2.0",
"nextjs-basic-auth-middleware": "^0.2.1",
"nodemailer": "^6.7.3",
"nookies": "^2.5.2",
"ospoint": "^0.2.1",
"path": "^0.12.7",
@@ -61,6 +68,7 @@
"sass": "1.32",
"sass-loader": "^12.4.0",
"swr": "^1.1.2",
"typeorm": "^0.3.5",
"webpack": "^5.66.0",
"xml2js": "^0.4.23",
"xmldom": "^0.6.0",
@@ -70,6 +78,7 @@
"devDependencies": {
"eslint": "^8.6.0",
"eslint-config-next": "^12.0.8",
"prettier": "2.5.1"
"prettier": "2.5.1",
"prisma": "^3.12.0"
}
}
+12 -6
View File
@@ -5,19 +5,25 @@ import { wrapper } from "../store/store";
import { useStore, Provider } from "react-redux";
import { persistStore } from "redux-persist";
import { PersistGate } from "redux-persist/integration/react";
import { SessionProvider as AuthProvider } from "next-auth/react";
const MyApp = ({ Component, pageProps }) => {
const MyApp = ({ Component, pageProps: { session, ...pageProps } }) => {
const store = useStore((state) => state);
const persistor = persistStore(store, {}, function () {
persistor.persist();
});
return (
<Provider store={store}>
<PersistGate persistor={store.__persistor} loading={<Loading />}>
<Component {...pageProps} />
</PersistGate>
</Provider>
<AuthProvider session={session}>
<Provider store={store}>
<PersistGate
persistor={store.__persistor}
loading={<Loading />}
>
<Component {...pageProps} />
</PersistGate>
</Provider>
</AuthProvider>
);
};
+129
View File
@@ -0,0 +1,129 @@
import NextAuth from "next-auth";
import EmailProvider from "next-auth/providers/email";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
import nodemailer from "nodemailer";
const prisma = new PrismaClient();
// async function sendVerificationRequest({
// identifier: email,
// url,
// provider: { server, from },
// }) {
// const { host } = new URL(url);
// const transport = nodemailer.createTransport(server);
// await transport.sendMail({
// to: email,
// from,
// subject: `Sign in to ${host}`,
// text: text({ url, host }),
// html: html({ url, host, email }),
// });
// }
// Email HTML body
function html({ url, host, email }) {
// Insert invisible space into domains and email address to prevent both the
// email address and the domain from being turned into a hyperlink by email
// clients like Outlook and Apple mail, as this is confusing because it seems
// like they are supposed to click on their email address to sign in.
const escapedEmail = `${email.replace(/\./g, "&#8203;.")}`;
const escapedHost = `${host.replace(/\./g, "&#8203;.")}`;
// Some simple styling options
const backgroundColor = "#ffffff";
const textColor = "#444444";
const mainBackgroundColor = "#ffffff";
const buttonBackgroundColor = "#aa1111";
const buttonBorderColor = "#aa1111";
const buttonTextColor = "#ffffff";
const headerBackgroundColor = "#000000";
return `
<body style="background: ${backgroundColor};">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: #fff;background-color: ${headerBackgroundColor};border-bottom: 10px solid #ffd530;>
<strong>PEDW - Planning Casework</strong>
</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="20" cellpadding="0" style="background: ${mainBackgroundColor}; max-width: 600px; margin: auto; border-radius: 10px;">
<tr>
<td align="center" style="padding: 10px 0px 0px 0px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
Sign in as <strong>${escapedEmail}</strong>
</td>
<img src ="http://localhost:3000/assets/images/planning-casework-en.svg"/>
</tr>
<tr>
<td align="center" style="padding: 20px 0;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 5px;" bgcolor="${buttonBackgroundColor}"><a href="${url}" target="_blank" style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${buttonTextColor}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${buttonBorderColor}; display: inline-block; font-weight: bold;">Sign in</a></td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
If you did not request this email you can safely ignore it.
</td>
</tr>
</table>
</body>
`;
}
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
function text({ url, host }) {
return `Sign in to PEDW - Planning Casework\n${url}\n\n`;
}
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export default NextAuth({
// https://next-auth.js.org/configuration/providers
providers: [
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
maxAge: 10 * 60, // Magic links are valid for 10 min only
async sendVerificationRequest({
identifier: email,
url,
provider: { server, from },
}) {
const { host } = new URL(url);
const transport = nodemailer.createTransport(server);
await transport.sendMail({
to: email,
from,
subject: `Sign in to PEDW - Planning Casework`,
text: text({ url, host }),
html: html({ url, host, email }),
});
},
}),
],
adapter: PrismaAdapter(prisma),
session: {
jwt: true,
pages: {},
theme: "dark",
debug: true,
},
pages: {
signIn: "/auth/signin",
//signOut: "/auth/signout",
error: "/auth/error", // Error code passed in query string as ?error=
verifyRequest: "/auth/verify-request", // (used for check email message)
// newUser: "/auth/new-user", // New users will be directed here on first sign in (leave the property out if not of interest)
},
});
+80
View File
@@ -0,0 +1,80 @@
import useTranslation from "next-translate/useTranslation";
import Head from "next/head";
import Link from "next/link";
import CookieBanner from "../../components/cookieBanner";
import Footer from "../../components/footer";
import Header from "../../components/header";
import Breadcrumbs from "../../components/breadcrumbs";
import { useRouter } from "next/router";
import { getCsrfToken } from "next-auth/react";
const Error = (props) => {
let { t, lang } = useTranslation();
const { footerLinks, pages, csrfToken } = props;
const { error } = useRouter().query;
const errors = {
Signin: "Try signing with a different account.",
OAuthSignin: "Try signing with a different account.",
OAuthCallback: "Try signing with a different account.",
OAuthCreateAccount: "Try signing with a different account.",
EmailCreateAccount: "Try signing with a different account.",
Callback: "Try signing with a different account.",
OAuthAccountNotLinked:
"To confirm your identity, sign in with the same account you used originally.",
EmailSignin: "Check your email address.",
CredentialsSignin:
"Sign in failed. Check the details you provided are correct.",
Verification:
"Unable to sign in. The sign in link is no longer valid.It may have been used already or it may have expired. ",
default: "Unable to sign in.",
};
const errorMessage = error && (errors[error] ?? errors.default);
return (
<div>
<Head>
<title>Sign in Error - {t("common:service-name")}</title>
</Head>
<div id="page_wrapper">
<CookieBanner />
<Header courseName={t("common:service-name")} />
<div className="govuk-width-container govuk-!-padding-bottom-9">
<Breadcrumbs />
<main className="govuk-main-wrapper govuk-!-padding-top-0 govuk-!-padding-bottom-9">
<div className="govuk-grid-row ">
<div className="govuk-grid-column-two-thirds ">
<h1>There has been an error </h1>
</div>
</div>
<div className="govuk-grid-row">
<div className="govuk-grid-column-two-thirds govuk-body">
{errorMessage}
</div>
</div>
<div className="govuk-grid-row">
<div className="govuk-grid-column-two-thirds govuk-body">
{" "}
<Link href="/">Return to Home</Link>
</div>
</div>
</main>
</div>
<Footer footerLinks={footerLinks} />
</div>
</div>
);
};
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context);
return {
props: { csrfToken },
};
}
export default Error;
+82
View File
@@ -0,0 +1,82 @@
import useTranslation from "next-translate/useTranslation";
import Head from "next/head";
import Link from "next/link";
import CookieBanner from "../../components/cookieBanner";
import Footer from "../../components/footer";
import Header from "../../components/header";
import Breadcrumbs from "../../components/breadcrumbs";
import { getCsrfToken } from "next-auth/react";
const SignIn = (props) => {
let { t, lang } = useTranslation();
const { footerLinks, pages, csrfToken } = props;
return (
<div>
<Head>
<title>Sign in - {t("common:service-name")}</title>
</Head>
<div id="page_wrapper">
<CookieBanner />
<Header courseName={t("common:service-name")} />
<div className="govuk-width-container govuk-!-padding-bottom-9">
<Breadcrumbs />
<main className="govuk-main-wrapper govuk-!-padding-top-0 govuk-!-padding-bottom-9">
<div className="govuk-grid-row ">
<div className="govuk-grid-column-two-thirds ">
<h1>Sign in</h1>
</div>
</div>
<div className="govuk-grid-row">
<div className="govuk-grid-column-two-thirds ">
<form
method="post"
action="/api/auth/signin/email"
>
<div className="govuk-form-group">
<input
className="govuk-input"
name="csrfToken"
type="hidden"
defaultValue={csrfToken}
/>
<label
className="govuk-label"
htmlFor="email"
>
Email address{" "}
</label>
<input
className="govuk-input govuk-!-width-three-quarters"
type="email"
id="email"
name="email"
/>
</div>
<div className="govuk-form-group">
<button
type="submit"
className="govuk-button"
>
Sign in with your email
</button>
</div>
</form>
</div>
</div>
</main>
</div>
<Footer footerLinks={footerLinks} />
</div>
</div>
);
};
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context);
return {
props: { csrfToken },
};
}
export default SignIn;
+59
View File
@@ -0,0 +1,59 @@
import useTranslation from "next-translate/useTranslation";
import Head from "next/head";
import Link from "next/link";
import CookieBanner from "../../components/cookieBanner";
import Footer from "../../components/footer";
import Header from "../../components/header";
import Breadcrumbs from "../../components/breadcrumbs";
import { getCsrfToken } from "next-auth/react";
const SignIn = (props) => {
let { t, lang } = useTranslation();
const { footerLinks, pages, csrfToken } = props;
return (
<div>
<Head>
<title>Sign in - {t("common:service-name")}</title>
</Head>
<div id="page_wrapper">
<CookieBanner />
<Header courseName={t("common:service-name")} />
<div className="govuk-width-container govuk-!-padding-bottom-9">
<Breadcrumbs />
<main className="govuk-main-wrapper govuk-!-padding-top-0 govuk-!-padding-bottom-9">
<div className="govuk-grid-row ">
<div className="govuk-grid-column-two-thirds ">
<h1>Check your email</h1>
</div>
</div>
<div className="govuk-grid-row">
<div className="govuk-grid-column-two-thirds govuk-body">
A link has been sent to your email so you can
continue
</div>
</div>
<div className="govuk-grid-row">
<div className="govuk-grid-column-two-thirds govuk-body">
{" "}
<Link href="/">Return to Home</Link>
</div>
</div>
</main>
</div>
<Footer footerLinks={footerLinks} />
</div>
</div>
);
};
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context);
return {
props: { csrfToken },
};
}
export default SignIn;
+16 -25
View File
@@ -19,6 +19,7 @@ import ServiceBanner from "../components/servicebanner";
import { setAccountDetails } from "../store/accountDetails/action";
import { getGovGatewayConfig } from "../store/govgateway/action";
import { wrapper } from "../store/store";
import { getSession, useSession, signIn, signOut } from "next-auth/react";
const Home = (props) => {
const {
@@ -43,7 +44,7 @@ const Home = (props) => {
//console.log("hashed email str", loginEmailStr);
_.isEmpty(loggedInUserId) != false && !hasGGCode
? console.log("no ggcode")
? console.log("no session")
: _.has(loggedInUserId, "value")
? _.isEmpty(loggedInUserId.value)
? router.replace({
@@ -159,16 +160,18 @@ export const getServerSideProps = wrapper.getServerSideProps(
(store) => async (ctx) => {
const { query, req, res } = ctx;
let thisSession = await getSession(ctx);
var relayToken = await getToken();
relayToken = relayToken.access_token;
console.log("gg code:", query.code);
console.log("gg local", query.ui_locales);
console.log("auth sess:", thisSession);
const showLoginCheck = process.env.SHOWLOGIN || false;
const showMapCheck = process.env.SHOWMAPS || false;
const hasLoginCode = typeof query.code != "undefined";
const hasLoginCode = thisSession != null;
let providerConfig = await getProviderConfig();
let ggLocale =
typeof query.ui_locales != "undefined" && query.ui_locales;
@@ -192,27 +195,15 @@ export const getServerSideProps = wrapper.getServerSideProps(
let loginEmailStr = "";
if (hasLoginCode == true) {
ggToken = await getGGToken(
providerConfig.token_endpoint,
query.code,
process.env.GG_REDIRECT_URI
);
_.has(ggToken, "access_token")
? ((ggUserInfo = await getUserInfo(
providerConfig.userinfo_endpoint,
ggToken.access_token
)),
(loginEmailStr = hashString(ggUserInfo.data.email)),
(portalUserObj = await getPortalLogin(
ggUserInfo.data.email,
relayToken
)),
console.log("loggin user stuff: ", portalUserObj),
store.dispatch(setAccountDetails(portalUserObj)))
: console.log(
ggToken.status + " " + ggToken.data.error_description
);
(loginEmailStr = hashString(thisSession.user.email)),
(portalUserObj = await getPortalLogin(
thisSession.user.email,
relayToken
)),
console.log("loggin user stuff: ", portalUserObj),
store.dispatch(setAccountDetails(portalUserObj));
} else {
console.log("no session");
}
if (ggToken.status == 400) {
+7 -1
View File
@@ -31,7 +31,7 @@ import {
} from "../../store/watchedCases/action";
const Home = (props) => {
const { footerLinks, pages, watchedCases } = props;
const { footerLinks, pages, watchedCases, showMap } = props;
let { t, lang } = useTranslation();
const router = useRouter();
@@ -112,6 +112,7 @@ const Home = (props) => {
<div className="govuk-width-container">
<Breadcrumbs slug={appealtypes} />
<DNSSearchResults
showMap={showMap}
searchString="DNS"
searchResultsObj={props.searchResultsObj}
myportal={true}
@@ -152,6 +153,8 @@ export const getServerSideProps = wrapper.getServerSideProps(
}
: searchResultsObj;
const showMapCheck = process.env.SHOWMAPS || false;
const [searchDetailsObj, watchedCasesDetails] = await Promise.all([
await getSearchDetails(searchResultsObj),
await getDetails(watchedCases, "myWatchedCases"),
@@ -163,6 +166,9 @@ export const getServerSideProps = wrapper.getServerSideProps(
store.dispatch(setDNSCoords(dnsCoords));
store.dispatch(setWatchedCases(watchedCases));
store.dispatch(setWatchedCasesDetails(watchedCasesDetails));
return {
props: { showMap: showMapCheck },
};
}
);
+1
View File
@@ -63,6 +63,7 @@ const Home = (props) => {
appealTypeID={
props.currentView.caseReference.appealType
}
showMap={props.currentView.caseReference.showMap}
/>
</div>
<Footer footerLinks={footerLinks} />
+8
View File
@@ -39,6 +39,8 @@ import {
} from "../../store/watchedCases/action";
import { setCookie } from "nookies";
import { getSession, useSession, signIn, signOut } from "next-auth/react";
const Home = (props) => {
const {
footerLinks,
@@ -56,6 +58,8 @@ const Home = (props) => {
const { locale } = router;
const { appealtypes } = router.query;
const { data: session } = useSession();
return (
<div>
<Head>
@@ -153,6 +157,10 @@ export const getServerSideProps = wrapper.getServerSideProps(
let loggedInUser = cookies.pinsUser;
let thisSession = await getSession(ctx);
console.log("nextAuth Session:", thisSession);
let providerConfig = await getProviderConfig();
store.dispatch(getGovGatewayConfig(providerConfig));
@@ -0,0 +1,57 @@
-- CreateTable
CREATE TABLE `accounts` (
`id` VARCHAR(191) NOT NULL,
`user_id` VARCHAR(191) NOT NULL,
`type` VARCHAR(191) NOT NULL,
`provider` VARCHAR(191) NOT NULL,
`provider_account_id` VARCHAR(191) NOT NULL,
`refresh_token` TEXT NULL,
`access_token` TEXT NULL,
`expires_at` INTEGER NULL,
`token_type` VARCHAR(191) NULL,
`scope` VARCHAR(191) NULL,
`id_token` TEXT NULL,
`session_state` VARCHAR(191) NULL,
UNIQUE INDEX `accounts_provider_provider_account_id_key`(`provider`, `provider_account_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `sessions` (
`id` VARCHAR(191) NOT NULL,
`session_token` VARCHAR(191) NOT NULL,
`user_id` VARCHAR(191) NOT NULL,
`expires` DATETIME(3) NOT NULL,
UNIQUE INDEX `sessions_session_token_key`(`session_token`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `users` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NULL,
`email` VARCHAR(191) NULL,
`email_verified` DATETIME(3) NULL,
`image` VARCHAR(191) NULL,
UNIQUE INDEX `users_email_key`(`email`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `verificationtokens` (
`identifier` VARCHAR(191) NOT NULL,
`token` VARCHAR(191) NOT NULL,
`expires` DATETIME(3) NOT NULL,
UNIQUE INDEX `verificationtokens_token_key`(`token`),
UNIQUE INDEX `verificationtokens_identifier_token_key`(`identifier`, `token`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `accounts` ADD CONSTRAINT `accounts_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sessions` ADD CONSTRAINT `sessions_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "mysql"
+61
View File
@@ -0,0 +1,61 @@
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Account {
id String @id @default(cuid())
userId String @map("user_id")
type String
provider String
providerAccountId String @map("provider_account_id")
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@map("accounts")
}
model Session {
id String @id @default(cuid())
sessionToken String @unique @map("session_token")
userId String @map("user_id")
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("sessions")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime? @map("email_verified")
image String?
accounts Account[]
sessions Session[]
@@map("users")
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
@@map("verificationtokens")
}
+3818 -2371
View File
File diff suppressed because it is too large Load Diff