feat: add login and print user object on my-pages

This commit is contained in:
Arvid Norlin
2024-03-11 17:27:57 +01:00
committed by Simon Emanuelsson
parent f1278a8d11
commit 70f9c22410
19 changed files with 396 additions and 46 deletions

View File

@@ -2,7 +2,13 @@ CMS_ACCESS_TOKEN=""
CMS_API_KEY=""
CMS_ENVIRONMENT="development"
CMS_URL="https://eu-graphql.contentstack.com/stacks/${CMS_API_KEY}?environment=${CMS_ENVIRONMENT}"
CMS_PREVIEW_URL="https://graphql-preview.contentstack.com/stacks/${CMS_API_KEY}?environment=${CMS_ENVIRONMENT}";
CMS_PREVIEW_URL="https://graphql-preview.contentstack.com/stacks/${CMS_API_KEY}?environment=${CMS_ENVIRONMENT}"
CMS_PREVIEW_TOKEN=""
ADOBE_SCRIPT_SRC=""
REVALIDATE_SECRET=""
DESIGN_SYSTEM_ACCESS_TOKEN=""
CURITY_CLIENT_ID_USER=""
CURITY_CLIENT_SECRET_USER=""
CURITY_ISSUER_USER="https://testlogin.scandichotels.com"
NEXTAUTH_URL="http://localhost:3000/api/auth"
NEXTAUTH_SECRET="secret"

2
.gitignore vendored
View File

@@ -34,3 +34,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
certificates

View File

@@ -0,0 +1,12 @@
import { auth } from "@/auth"
export default async function MyPages() {
const session = await auth()
console.log({ session })
return (
<>
<h2>Wilkommen</h2>
<pre>{JSON.stringify(session, null, 2)}</pre>
</>
)
}

View File

@@ -0,0 +1,21 @@
import { signIn } from "@/auth"
import { pageNames } from "@/constants/myPages"
import type { LangParams, Params } from "@/types/params"
export default async function Page({ params }: Params<LangParams>) {
async function login() {
"use server"
await signIn("curity", {
redirectTo: `/${params.lang}/${pageNames[params.lang]}`,
})
}
return (
<main>
<form action={login}>
<button type="submit">Sign In</button>
</form>
</main>
)
}

View File

@@ -0,0 +1,2 @@
export { GET, POST } from "@/auth"
export const runtime = "edge"

View File

@@ -1,7 +1,7 @@
import { env } from "@/env/server"
import { revalidateTag } from "next/cache"
import type { NextRequest } from "next/server"
import { NextRequest } from "next/server"
export async function POST(request: NextRequest) {
try {

116
auth.ts Normal file
View File

@@ -0,0 +1,116 @@
import NextAuth from "next-auth"
import { env } from "@/env/server"
import type { NextAuthConfig } from "next-auth"
export const config = {
providers: [
{
id: "curity",
type: "oidc",
name: "Curity",
// FIXME: This is incorrect. We should not hard code this.
// It should be ${env.CURITY_ISSUER_USER}.
// This change requires sync between Curity deploy and CurrentWeb and NewWeb.
issuer: "https://scandichotels.com",
token: {
url: `${env.CURITY_ISSUER_USER}/oauth/v2/token`,
},
userinfo: {
url: `${env.CURITY_ISSUER_USER}/oauth/v2/userinfo`,
},
authorization: {
url: `${env.CURITY_ISSUER_USER}/oauth/v2/authorize`,
params: {
scope: ["openid"],
},
},
clientId: env.CURITY_CLIENT_ID_USER,
clientSecret: env.CURITY_CLIENT_SECRET_USER,
profile(profile: { id: string; sub: string; given_name: string }) {
console.log({ profile })
return {
id: profile.id,
sub: profile.sub,
given_name: profile.given_name,
}
},
},
],
trustHost: true,
// pages: {
// signIn: "/auth/login",
// },
// basePath: "/api/auth",
session: {
strategy: "jwt",
},
callbacks: {
async signIn(...args) {
console.log("****** SIGN IN *******")
console.log(args)
return true
},
async session(...args) {
console.log(args)
return args[0].session
},
async redirect({ baseUrl, url }) {
console.log("****** REDIRECT *******")
console.log({ url })
console.log({ baseUrl })
// Allows relative callback URLs
if (url.startsWith("/")) {
return `${baseUrl}${url}`
} else if (new URL(url).origin === baseUrl) {
// Allows callback URLs on the same origin
return url
}
return baseUrl
},
authorized({ auth, request }) {
console.log("****** AUTHORIZED *******")
console.log({ request, auth })
// const { pathname } = request.nextUrl
// if (pathname === "/middleware-example") return !!auth
return true
},
jwt({ session, token, trigger }) {
console.log("****** JWT *******")
// if (trigger === "update") token.name = session.user.name
console.log({ token, trigger, session })
return token
},
},
events: {
async signIn(...args) {
console.log({ args })
},
async session(...args) {
console.log({ args })
},
},
logger: {
error(code, ...message) {
console.info("ERROR LOGGER")
console.error(code, message)
},
warn(code, ...message) {
console.info("WARN LOGGER")
console.warn(code, message)
},
debug(code, ...message) {
console.info("DEBUG LOGGER")
console.debug(code, message)
},
},
} satisfies NextAuthConfig
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth(config)

View File

@@ -1,4 +1,4 @@
import { langEnum } from "@/types/lang"
import { langEnum, type Lang } from "@/types/lang"
export const localeToLang = {
en: langEnum.en,
@@ -39,3 +39,13 @@ export const localeToLang = {
"nn-NO": langEnum.no,
"se-NO": langEnum.no,
}
export const DEFAULT_LOCALE = "en"
export const locales: Lang[] = ["da", "de", DEFAULT_LOCALE, "fi", "no", "sv"]
export function findLocale(pathname: string) {
let locale = locales.find(
(loc) => pathname.startsWith(`/${loc}/`) || pathname === `/${loc}`
)
return locale
}

8
constants/myPages.js Normal file
View File

@@ -0,0 +1,8 @@
export const pageNames = {
da: "mine-sider",
de: "mein-profil",
en: "my-pages",
fi: "minun-sivujani",
no: "mine-sider",
sv: "mina-sidor",
}

10
constants/myPages.ts Normal file
View File

@@ -0,0 +1,10 @@
import type { Lang } from "@/types/lang"
export const pageNames: Record<Lang, string> = {
da: "mine-sider",
de: "mein-profil",
en: "my-pages",
fi: "minun-sivujani",
no: "mine-sider",
sv: "mina-sidor",
}

6
env/server.ts vendored
View File

@@ -16,6 +16,9 @@ export const env = createEnv({
PRINT_QUERY: z.boolean().default(false),
REVALIDATE_SECRET: z.string(),
DESIGN_SYSTEM_ACCESS_TOKEN: z.string(),
CURITY_CLIENT_ID_USER: z.string(),
CURITY_CLIENT_SECRET_USER: z.string(),
CURITY_ISSUER_USER: z.string(),
},
emptyStringAsUndefined: true,
runtimeEnv: {
@@ -32,5 +35,8 @@ export const env = createEnv({
PRINT_QUERY: process.env.PRINT_QUERY,
REVALIDATE_SECRET: process.env.REVALIDATE_SECRET,
DESIGN_SYSTEM_ACCESS_TOKEN: process.env.DESIGN_SYSTEM_ACCESS_TOKEN,
CURITY_CLIENT_ID_USER: process.env.CURITY_CLIENT_ID_USER,
CURITY_CLIENT_SECRET_USER: process.env.CURITY_CLIENT_SECRET_USER,
CURITY_ISSUER_USER: process.env.CURITY_ISSUER_USER,
},
})

View File

@@ -1,42 +1,63 @@
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
// This function can be marked `async` if using `await` inside
export async function middleware(request: NextRequest) {
// const locales = await fetch(CMS_API, {
// locales: true
// })
const locales = ["en", "sv", "no", "fi", "da", "de"]
import { auth } from "@/auth"
const locale = locales.find(
(locale) =>
request.nextUrl.pathname.startsWith(`/${locale}/`) ||
request.nextUrl.pathname === `/${locale}`
)
import { findLocale } from "@/constants/locales"
import { pageNames } from "@/constants/myPages"
import { apiAuthPrefix } from "@/routes/api"
// import { publicRoutes } from "@/routes/public"
import { protectedRoutes } from "@/routes/protected"
export default auth(async function middleware(request) {
const { nextUrl } = request
const isLoggedIn = !!request.auth
const isApiRoute = nextUrl.pathname.startsWith(apiAuthPrefix)
if (isApiRoute) {
return NextResponse.next()
}
const locale = findLocale(nextUrl.pathname)
if (!locale) {
//return <LocalePicker />
return Response.json("Not found!!!", { status: 404 })
}
// const data = await fetch(CMS_API, {
// uri: request.nextUrl.pathname,
// locale
// }).json()
const isProtectedRoute = protectedRoutes.includes(nextUrl.pathname)
if (isProtectedRoute) {
if (isLoggedIn) {
/**
* Temporary hard rewrite to my pages
*/
return NextResponse.rewrite(
new URL(`/${locale}/${pageNames[locale]}`, nextUrl)
)
} else {
/**
* Redirect to Loginpage
* (Loginpage most likely to be removed)
*/
return NextResponse.redirect(new URL(`/${locale}/login`, nextUrl))
}
}
if (nextUrl.pathname.startsWith(`/${locale}/login`)) {
return NextResponse.next()
}
// const isPublicRoute = publicRoutes.includes(nextUrl.pathname)
// if (!isLoggedIn && !isPublicRoute) {
// return NextResponse.redirect(new URL(`/${locale}/login`, nextUrl))
// }
//const contentType = data.response.meta.contentType;
const contentType = "currentContentPage"
const pathNameWithoutLocale = request.nextUrl.pathname.replace(
`/${locale}`,
""
)
const pathNameWithoutLocale = nextUrl.pathname.replace(`/${locale}`, "")
const searchParams = new URLSearchParams(request.nextUrl.searchParams)
if (request.nextUrl.pathname.includes("preview")) {
searchParams.set("uri", pathNameWithoutLocale.replace("/preview", ""))
return NextResponse.rewrite(
new URL(
`/${locale}/preview-current?${searchParams.toString()}`,
@@ -46,7 +67,6 @@ export async function middleware(request: NextRequest) {
}
searchParams.set("uri", pathNameWithoutLocale)
switch (contentType) {
case "currentContentPage":
return NextResponse.rewrite(
@@ -56,19 +76,16 @@ export async function middleware(request: NextRequest) {
)
)
}
return NextResponse.redirect(new URL("/home", request.url))
}
return NextResponse.next()
})
// See "Matching Paths" below to learn more
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|_static|imageVault|contentassets|favicon.ico|en/test).*)",
],
/**
* Copied from Clerk to protect all routes by default and handle
* public routes inside middleware.
* (https://clerk.com/docs/quickstarts/nextjs?utm_source=sponsorship&utm_medium=youtube&utm_campaign=code-with-antonio&utm_content=12-31-2023#add-authentication-to-your-app)
*/
matcher: ["/((?!.+\\.[\\w]+$|_next|en/test).*)", "/", "/(api)(.*)"],
}

View File

@@ -1,5 +1,5 @@
import createJiti from "jiti"
import { pageNames } from "./constants/myPages.js"
const jiti = createJiti(new URL(import.meta.url).pathname)
jiti("./env/server")
@@ -9,10 +9,6 @@ jiti("./env/client")
const nextConfig = {
eslint: { ignoreDuringBuilds: true },
generateBuildId: async () => {
return process.env.BUILD_ID
},
images: {
remotePatterns: [
{
@@ -40,6 +36,17 @@ const nextConfig = {
return config
},
rewrites() {
return {
beforeFiles: [
{ source: `/da/${pageNames.da}`, destination: "/da/my-pages" },
{ source: `/de/${pageNames.de}`, destination: "/de/my-pages" },
{ source: `/fi/${pageNames.fi}`, destination: "/fi/my-pages" },
{ source: `/no/${pageNames.no}`, destination: "/no/my-pages" },
{ source: `/sv/${pageNames.sv}`, destination: "/sv/my-pages" },
],
}
},
}
export default nextConfig

119
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"graphql-request": "^6.1.0",
"graphql-tag": "^2.12.6",
"next": "^14.1.0",
"next-auth": "^5.0.0-beta.15",
"react": "^18",
"react-dom": "^18",
"server-only": "^0.0.1",
@@ -52,6 +53,36 @@
"node": ">=0.10.0"
}
},
"node_modules/@auth/core": {
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.28.0.tgz",
"integrity": "sha512-/fh/tb/L4NMSYcyPoo4Imn8vN6MskcVfgESF8/ndgtI4fhD/7u7i5fTVzWgNRZ4ebIEGHNDbWFRxaTu1NtQgvA==",
"dependencies": {
"@panva/hkdf": "^1.1.1",
"@types/cookie": "0.6.0",
"cookie": "0.6.0",
"jose": "^5.1.3",
"oauth4webapi": "^2.4.0",
"preact": "10.11.3",
"preact-render-to-string": "5.2.3"
},
"peerDependencies": {
"@simplewebauthn/browser": "^9.0.1",
"@simplewebauthn/server": "^9.0.2",
"nodemailer": "^6.8.0"
},
"peerDependenciesMeta": {
"@simplewebauthn/browser": {
"optional": true
},
"@simplewebauthn/server": {
"optional": true
},
"nodemailer": {
"optional": true
}
}
},
"node_modules/@babel/runtime": {
"version": "7.23.8",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz",
@@ -625,6 +656,14 @@
"node": ">= 8"
}
},
"node_modules/@panva/hkdf": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz",
"integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -2268,6 +2307,11 @@
"@types/responselike": "^1.0.0"
}
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
"node_modules/@types/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
@@ -3692,6 +3736,14 @@
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookies": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
@@ -6513,6 +6565,14 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jose": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.2.3.tgz",
"integrity": "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -7749,6 +7809,32 @@
}
}
},
"node_modules/next-auth": {
"version": "5.0.0-beta.15",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.15.tgz",
"integrity": "sha512-UQggNq8CDu3/w8CYkihKLLnRPNXel98K0j7mtjj9a6XTNYo4Hni8xg/2h1YhElW6vXE8mgtvmH11rU8NKw86jQ==",
"dependencies": {
"@auth/core": "0.28.0"
},
"peerDependencies": {
"@simplewebauthn/browser": "^9.0.1",
"@simplewebauthn/server": "^9.0.2",
"next": "^14",
"nodemailer": "^6.6.5",
"react": "^18.2.0"
},
"peerDependenciesMeta": {
"@simplewebauthn/browser": {
"optional": true
},
"@simplewebauthn/server": {
"optional": true
},
"nodemailer": {
"optional": true
}
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -7828,6 +7914,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/oauth4webapi": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.10.3.tgz",
"integrity": "sha512-9FkXEXfzVKzH63GUOZz1zMr3wBaICSzk6DLXx+CGdrQ10ItNk2ePWzYYc1fdmKq1ayGFb2aX97sRCoZ2s0mkDw==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -8306,6 +8400,26 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/preact": {
"version": "10.11.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
"integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/preact-render-to-string": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz",
"integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==",
"dependencies": {
"pretty-format": "^3.8.0"
},
"peerDependencies": {
"preact": ">=10"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -8342,6 +8456,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pretty-format": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",

View File

@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"build": "next build",
"dev": "next dev",
"dev": "rm -rf .next && next dev",
"lint": "next lint && tsc",
"prepare": "husky install",
"start": "node .next/standalone/server.js",
@@ -26,6 +26,7 @@
"graphql-request": "^6.1.0",
"graphql-tag": "^2.12.6",
"next": "^14.1.0",
"next-auth": "^5.0.0-beta.15",
"react": "^18",
"react-dom": "^18",
"server-only": "^0.0.1",
@@ -50,4 +51,4 @@
"engines": {
"node": "18"
}
}
}

1
routes/api.ts Normal file
View File

@@ -0,0 +1 @@
export const apiAuthPrefix = "/api/auth"

10
routes/protected.ts Normal file
View File

@@ -0,0 +1,10 @@
import { pageNames } from "@/constants/myPages"
import type { Lang } from "@/types/lang"
/* Authenticated routes */
export const protectedRoutes: string[] = [
...Object.keys(pageNames).map(
(locale) => `/${locale}/${pageNames[locale as Lang]}`
),
]

2
routes/public.ts Normal file
View File

@@ -0,0 +1,2 @@
/* Unauthenticated routes */
export const publicRoutes: string[] = ["/"]