Skip to content

Commit

Permalink
chore(apps): add separate apps/dev/express app (#10736)
Browse files Browse the repository at this point in the history
  • Loading branch information
ndom91 committed May 8, 2024
1 parent b7d1f9e commit fbc89ea
Show file tree
Hide file tree
Showing 20 changed files with 754 additions and 93 deletions.
7 changes: 7 additions & 0 deletions apps/dev/express/.env.example
@@ -0,0 +1,7 @@
AUTH_SECRET=

AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=

AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=
21 changes: 21 additions & 0 deletions apps/dev/express/.gitignore
@@ -0,0 +1,21 @@
# API keys and secrets
.env

# Dependency directory
node_modules

# Editors
.idea
*.iml
.vscode/settings.json

# OS metadata
.DS_Store
Thumbs.db

# Ignore built ts files
dist/**/*

# Ignore built css files
/public/css/output.css

14 changes: 14 additions & 0 deletions apps/dev/express/.prettierignore
@@ -0,0 +1,14 @@

.DS_Store
node_modules
/dist
/.turbo
/package
.env
.env.*
!.env.example

# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
28 changes: 28 additions & 0 deletions apps/dev/express/README.md
@@ -0,0 +1,28 @@
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/express). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
<p align="center">
<br/>
<a href="https://authjs.dev" target="_blank"><img width="150px" src="https://authjs.dev/img/logo-sm.png" /></a>
<h3 align="center">Auth.js Example App with <a href="https://expressjs.com">Express</a></h3>
<p align="center">
Open Source. Full Stack. Own Your Data.
</p>
<p align="center" style="align: center;">
<a href="https://npm.im/@auth/express">
<img alt="npm" src="https://img.shields.io/npm/v/@auth/express?color=green&label=@auth/express&style=flat-square">
</a>
<a href="https://bundlephobia.com/result?p=@auth/express">
<img src="https://img.shields.io/bundlephobia/minzip/@auth/express?label=size&style=flat-square" alt="Bundle Size"/>
</a>
<a href="https://www.npmtrends.com/@auth/express">
<img src="https://img.shields.io/npm/dm/@auth/express?label=%20downloads&style=flat-square" alt="Downloads" />
</a>
<a href="https://npm.im/next-auth">
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
</a>
</p>
</p>

# Documentation

- [express.authjs.dev](https://express.authjs.dev)
3 changes: 3 additions & 0 deletions apps/dev/express/api/index.js
@@ -0,0 +1,3 @@
import { app } from "../src/app.js"

export default app
35 changes: 35 additions & 0 deletions apps/dev/express/package.json
@@ -0,0 +1,35 @@
{
"name": "express-auth-app",
"description": "Express + Auth.js Developer app",
"type": "module",
"private": true,
"scripts": {
"start": "node --env-file=.env dist/server.js",
"clean": "rm -rf dist",
"build": "pnpm build:ts && pnpm build:css",
"build:ts": "tsc",
"build:css": "tailwindcss -i ./public/css/style.css -o ./public/css/output.css",
"dev": "tsx watch --env-file=.env src/server.ts & pnpm build:css -w",
"lint": "eslint src/*.ts --fix",
"prettier": "prettier src/*.ts --write"
},
"author": "Auth.js Team (https://authjs.dev/contributors)",
"license": "MIT",
"dependencies": {
"@auth/express": "workspace:*",
"express": "^4.19.2",
"morgan": "^1.10.0",
"pug": "^3.0.2"
},
"devDependencies": {
"@prettier/plugin-pug": "^3.0.0",
"@types/express": "^4.17.21",
"@types/morgan": "^1.9.9",
"@types/pug": "^2.0.10",
"tsx": "^4.7.3",
"typescript": "5.4.5"
},
"engines": {
"node": ">=20.11.0"
}
}
5 changes: 5 additions & 0 deletions apps/dev/express/public/css/style.css
@@ -0,0 +1,5 @@
@tailwind base;

@tailwind components;

@tailwind utilities;
71 changes: 71 additions & 0 deletions apps/dev/express/src/app.ts
@@ -0,0 +1,71 @@
import express, { type Request, type Response } from "express"
import logger from "morgan"
import { join } from "node:path"

import {
errorHandler,
errorNotFoundHandler,
} from "./middleware/error.middleware.js"

import {
authenticatedUser,
currentSession,
} from "./middleware/auth.middleware.js"
import { ExpressAuth } from "@auth/express"
import { authConfig } from "./config/auth.config.js"
import * as pug from "pug"

export const app = express()

app.set("port", process.env.PORT || 3004)

// @ts-expect-error (https://stackoverflow.com/questions/45342307/error-cannot-find-module-pug)
app.engine("pug", pug.__express)
app.set("views", join(import.meta.dirname, "..", "views"))
app.set("view engine", "pug")

// Trust Proxy for Proxies (Heroku, Render.com, Docker behind Nginx, etc)
// https://stackoverflow.com/questions/40459511/in-express-js-req-protocol-is-not-picking-up-https-for-my-secure-link-it-alwa
app.set("trust proxy", true)

app.use(logger("dev"))

// Serve static files
// NB: Uncomment this out if you want Express to serve static files for you vs. using a
// hosting provider which does so for you (for example through a CDN).
// app.use(express.static(join(import.meta.dirname, "..", "public")))

// Parse incoming requests data
app.use(express.urlencoded({ extended: true }))
app.use(express.json())

// Set session in res.locals
app.use(currentSession)

// Set up ExpressAuth to handle authentication
// IMPORTANT: It is highly encouraged set up rate limiting on this route
app.use("/api/auth/*", ExpressAuth(authConfig))

// Routes
app.get("/protected", async (_req: Request, res: Response) => {
res.render("protected", { session: res.locals.session })
})

app.get(
"/api/protected",
authenticatedUser,
async (_req: Request, res: Response) => {
res.json(res.locals.session)
},
)

app.get("/", async (_req: Request, res: Response) => {
res.render("index", {
title: "Express Auth Example",
user: res.locals.session?.user,
})
})

// Error handlers
app.use(errorNotFoundHandler)
app.use(errorHandler)
69 changes: 69 additions & 0 deletions apps/dev/express/src/config/auth.config.ts
@@ -0,0 +1,69 @@
import Apple from "@auth/express/providers/apple"
import Auth0 from "@auth/express/providers/auth0"
import AzureB2C from "@auth/express/providers/azure-ad-b2c"
import BoxyHQSAML from "@auth/express/providers/boxyhq-saml"
import Cognito from "@auth/express/providers/cognito"
import Coinbase from "@auth/express/providers/coinbase"
import Discord from "@auth/express/providers/discord"
import Dropbox from "@auth/express/providers/dropbox"
import Facebook from "@auth/express/providers/facebook"
import GitHub from "@auth/express/providers/github"
import Gitlab from "@auth/express/providers/gitlab"
import Google from "@auth/express/providers/google"
import Hubspot from "@auth/express/providers/hubspot"
import Keycloak from "@auth/express/providers/keycloak"
import LinkedIn from "@auth/express/providers/linkedin"
import Netlify from "@auth/express/providers/netlify"
import Okta from "@auth/express/providers/okta"
import Passage from "@auth/express/providers/passage"
import Pinterest from "@auth/express/providers/pinterest"
import Reddit from "@auth/express/providers/reddit"
import Slack from "@auth/express/providers/slack"
import Spotify from "@auth/express/providers/spotify"
import Twitch from "@auth/express/providers/twitch"
import Twitter from "@auth/express/providers/twitter"
import WorkOS from "@auth/express/providers/workos"
import Zoom from "@auth/express/providers/zoom"

export const authConfig = {
trustHost: true,
debug: process.env.NODE_ENV !== "production" ? true : false,
providers: [
Apple,
Auth0,
AzureB2C({
clientId: process.env.AUTH_AZURE_AD_B2C_ID,
clientSecret: process.env.AUTH_AZURE_AD_B2C_SECRET,
issuer: process.env.AUTH_AZURE_AD_B2C_ISSUER,
}),
BoxyHQSAML({
clientId: "dummy",
clientSecret: "dummy",
issuer: process.env.AUTH_BOXYHQ_SAML_ISSUER,
}),
Cognito,
Coinbase,
Discord,
Dropbox,
Facebook,
GitHub,
Gitlab,
Google,
Hubspot,
Keycloak,
LinkedIn,
Netlify,
Okta,
Passage,
Pinterest,
Reddit,
Slack,
Spotify,
Twitch,
Twitter,
WorkOS({
connection: process.env.AUTH_WORKOS_CONNECTION!,
}),
Zoom,
],
}
14 changes: 14 additions & 0 deletions apps/dev/express/src/errors.ts
@@ -0,0 +1,14 @@
export class HttpError extends Error {
status: number
constructor(status: number, message: string) {
super(message)
this.status = status
}
}

export class NotFoundError extends HttpError {
constructor(message: string, status = 404) {
super(status, message)
this.name = "NotFoundError"
}
}
29 changes: 29 additions & 0 deletions apps/dev/express/src/middleware/auth.middleware.ts
@@ -0,0 +1,29 @@
import { getSession } from "@auth/express"
import { authConfig } from "../config/auth.config.js"
import type { NextFunction, Request, Response } from "express"

export async function authenticatedUser(
req: Request,
res: Response,
next: NextFunction
) {
const session = res.locals.session ?? (await getSession(req, authConfig))

res.locals.session = session

if (session) {
return next()
}

res.status(400).json({ message: "Not Authenticated" })
}

export async function currentSession(
req: Request,
res: Response,
next: NextFunction
) {
const session = await getSession(req, authConfig)
res.locals.session = session
return next()
}
24 changes: 24 additions & 0 deletions apps/dev/express/src/middleware/error.middleware.ts
@@ -0,0 +1,24 @@
import type { NextFunction, Request, Response } from "express"
import { HttpError, NotFoundError } from "../errors.js"

export const errorHandler = (
err: HttpError | Error,
_req: Request,
res: Response,
_next: NextFunction
): void => {
// Render the error page
res.status(("status" in err && err.status) || 500)
res.render("error", {
title: "status" in err ? err.status : err.name,
message: err.message,
})
}

export const errorNotFoundHandler = (
_req: Request,
_res: Response,
next: NextFunction
): void => {
next(new NotFoundError("Not Found"))
}
9 changes: 9 additions & 0 deletions apps/dev/express/src/server.ts
@@ -0,0 +1,9 @@
import { app } from "./app.js"

const port = app.get("port")

const server = app.listen(port, () => {
console.log(`Listening on port ${port}`)
})

export default server
16 changes: 16 additions & 0 deletions apps/dev/express/tsconfig.json
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "NodeNext",
"esModuleInterop": true,
"target": "esnext",
"noImplicitAny": true,
"moduleResolution": "NodeNext",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"skipLibCheck": true,
"strict": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
5 changes: 5 additions & 0 deletions apps/dev/express/views/error.pug
@@ -0,0 +1,5 @@
extends layout

block content
h1=title
p=message
11 changes: 11 additions & 0 deletions apps/dev/express/views/index.pug
@@ -0,0 +1,11 @@
extends layout

block content
h1=title
p
| This is an example site to demonstrate how to use #{ ' ' }
a(href="https://expressjs.com/") Express
| #{ ' ' } with #{ ' ' }
a(href="https://authjs.dev/reference/express") Express Auth
|
| for authentication.

0 comments on commit fbc89ea

Please sign in to comment.