Skip to content

Commit 4a404b3

Browse files
authored
Merge pull request #1 from songkeys/main
feat: add fastify example
2 parents 16af091 + 83d971b commit 4a404b3

26 files changed

+628
-47
lines changed

Diff for: apps/dev/fastify/.env.example

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
AUTH_SECRET=
2+
3+
AUTH_GITHUB_ID=
4+
AUTH_GITHUB_SECRET=
5+
6+
AUTH_GOOGLE_ID=
7+
AUTH_GOOGLE_SECRET=

Diff for: apps/dev/fastify/.gitignore

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# API keys and secrets
2+
.env
3+
4+
# Dependency directory
5+
node_modules
6+
7+
# Editors
8+
.idea
9+
*.iml
10+
.vscode/settings.json
11+
12+
# OS metadata
13+
.DS_Store
14+
Thumbs.db
15+
16+
# Ignore built ts files
17+
dist/**/*
18+
19+
# Ignore built css files
20+
/public/css/output.css
21+

Diff for: apps/dev/fastify/.prettierignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
.DS_Store
3+
node_modules
4+
/dist
5+
/.turbo
6+
/package
7+
.env
8+
.env.*
9+
!.env.example
10+
11+
# Ignore files for PNPM, NPM and YARN
12+
pnpm-lock.yaml
13+
package-lock.json
14+
yarn.lock

Diff for: apps/dev/fastify/README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/fastify). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
2+
3+
<p align="center">
4+
<br/>
5+
<a href="https://authjs.dev" target="_blank"><img width="150px" src="https://authjs.dev/img/logo-sm.png" /></a>
6+
<h3 align="center">Auth.js Example App with <a href="https://fastify.dev">Fastify</a></h3>
7+
<p align="center">
8+
Open Source. Full Stack. Own Your Data.
9+
</p>
10+
<p align="center" style="align: center;">
11+
<a href="https://npm.im/@auth/fastify">
12+
<img alt="npm" src="https://img.shields.io/npm/v/@auth/fastify?color=green&label=@auth/fastify&style=flat-square">
13+
</a>
14+
<a href="https://bundlephobia.com/result?p=@auth/fastify">
15+
<img src="https://img.shields.io/bundlephobia/minzip/@auth/fastify?label=size&style=flat-square" alt="Bundle Size"/>
16+
</a>
17+
<a href="https://www.npmtrends.com/@auth/fastify">
18+
<img src="https://img.shields.io/npm/dm/@auth/fastify?label=%20downloads&style=flat-square" alt="Downloads" />
19+
</a>
20+
<a href="https://npm.im/next-auth">
21+
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
22+
</a>
23+
</p>
24+
</p>
25+
26+
# Documentation
27+
28+
- [fastify.authjs.dev](https://fastify.authjs.dev)

Diff for: apps/dev/fastify/api/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { app } from "../src/app.js"
2+
3+
export default app

Diff for: apps/dev/fastify/package.json

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "fastify-auth-app",
3+
"description": "Fastify + Auth.js Developer app",
4+
"type": "module",
5+
"private": true,
6+
"scripts": {
7+
"start": "node dist/server.js",
8+
"clean": "rm -rf dist",
9+
"build": "pnpm build:ts && pnpm build:css",
10+
"build:ts": "tsc",
11+
"build:css": "tailwindcss -i ./public/css/style.css -o ./public/css/output.css",
12+
"dev": "tsx watch --env-file=.env src/server.ts & pnpm build:css -w",
13+
"debug": "tsx watch --inspect --env-file=.env src/server.ts & pnpm build:css -w",
14+
"lint": "eslint src/*.ts --fix",
15+
"prettier": "prettier src/*.ts --write"
16+
},
17+
"author": "Songkeys (https://github.com/songkeys)",
18+
"license": "MIT",
19+
"dependencies": {
20+
"@auth/fastify": "workspace:*",
21+
"fastify": "^5.0.0",
22+
"@fastify/view": "^10.0.1",
23+
"@fastify/static": "^8.0.1",
24+
"pug": "^3.0.2",
25+
"tailwindcss": "^3.4.3"
26+
},
27+
"devDependencies": {
28+
"@prettier/plugin-pug": "^3.0.0",
29+
"@types/morgan": "^1.9.9",
30+
"@types/pug": "^2.0.10",
31+
"tsx": "^4.7.3",
32+
"typescript": "5.4.5"
33+
}
34+
}

Diff for: apps/dev/fastify/public/css/style.css

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@tailwind base;
2+
3+
@tailwind components;
4+
5+
@tailwind utilities;

Diff for: apps/dev/fastify/src/app.ts

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import Fastify from "fastify"
2+
3+
import * as path from "node:path"
4+
import {
5+
errorHandler,
6+
errorNotFoundHandler,
7+
} from "./middleware/error.middleware.js"
8+
import {
9+
authenticatedApi,
10+
authenticatedPage,
11+
currentSession,
12+
} from "./middleware/auth.middleware.js"
13+
14+
import { FastifyAuth, type Session } from "@auth/fastify"
15+
import { authConfig } from "./config/auth.config.js"
16+
17+
import * as pug from "pug"
18+
import fastifyView from "@fastify/view"
19+
import fastifyStatic from "@fastify/static"
20+
21+
declare module "fastify" {
22+
interface FastifyReply {
23+
session: Session | null
24+
}
25+
}
26+
27+
// Trust Proxy for Proxies (Heroku, Render.com, Docker behind Nginx, etc)
28+
export const fastify = Fastify({ trustProxy: true, logger: true })
29+
30+
// Decorating the reply is not required but will optimise performance
31+
// Only decorate the reply with a value type like null, as reference types like objects are shared among all requests, creating a security risk.
32+
fastify.decorateReply("session", null)
33+
34+
fastify.register(fastifyView, {
35+
engine: {
36+
pug,
37+
},
38+
root: path.join(import.meta.dirname, "..", "views"),
39+
})
40+
41+
// Serve static files
42+
// NB: Uncomment this out if you want Fastify to serve static files for you vs. using a
43+
// hosting provider which does so for you (for example through a CDN).
44+
fastify.register(fastifyStatic, {
45+
root: path.join(import.meta.dirname, "..", "public"),
46+
})
47+
48+
// Set session in reply
49+
fastify.addHook("preHandler", currentSession)
50+
51+
// Set up FastifyAuth to handle authentication
52+
// IMPORTANT: It is highly encouraged set up rate limiting on this route
53+
fastify.register(FastifyAuth(authConfig), { prefix: "/api/auth" })
54+
55+
// Routes
56+
fastify.get(
57+
"/protected",
58+
{ preHandler: [authenticatedPage] },
59+
async (req, reply) => {
60+
return reply.view("protected", { session: reply.session })
61+
}
62+
)
63+
64+
fastify.get(
65+
"/api/protected",
66+
{ preHandler: [authenticatedApi] },
67+
async (req, reply) => {
68+
return reply.send(reply.session)
69+
}
70+
)
71+
72+
fastify.get("/", async (_req, reply) => {
73+
return reply.view("index", {
74+
title: "Fastify Auth Example",
75+
user: reply.session?.user,
76+
})
77+
})
78+
79+
fastify.get("/2", async (_req, reply) => {
80+
return reply.view("index", {
81+
title: "Fastify Auth Example",
82+
user: reply.session?.user,
83+
})
84+
})
85+
86+
fastify.setErrorHandler(errorHandler)
87+
fastify.setNotFoundHandler(errorNotFoundHandler)

Diff for: apps/dev/fastify/src/config/auth.config.ts

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import Apple from "@auth/fastify/providers/apple"
2+
import Auth0 from "@auth/fastify/providers/auth0"
3+
import AzureB2C from "@auth/fastify/providers/azure-ad-b2c"
4+
import BoxyHQSAML from "@auth/fastify/providers/boxyhq-saml"
5+
import Cognito from "@auth/fastify/providers/cognito"
6+
import Coinbase from "@auth/fastify/providers/coinbase"
7+
import Discord from "@auth/fastify/providers/discord"
8+
import Dropbox from "@auth/fastify/providers/dropbox"
9+
import Facebook from "@auth/fastify/providers/facebook"
10+
import GitHub from "@auth/fastify/providers/github"
11+
import Gitlab from "@auth/fastify/providers/gitlab"
12+
import Google from "@auth/fastify/providers/google"
13+
import Hubspot from "@auth/fastify/providers/hubspot"
14+
import Keycloak from "@auth/fastify/providers/keycloak"
15+
import LinkedIn from "@auth/fastify/providers/linkedin"
16+
import Netlify from "@auth/fastify/providers/netlify"
17+
import Okta from "@auth/fastify/providers/okta"
18+
import Passage from "@auth/fastify/providers/passage"
19+
import Pinterest from "@auth/fastify/providers/pinterest"
20+
import Reddit from "@auth/fastify/providers/reddit"
21+
import Slack from "@auth/fastify/providers/slack"
22+
import Spotify from "@auth/fastify/providers/spotify"
23+
import Twitch from "@auth/fastify/providers/twitch"
24+
import Twitter from "@auth/fastify/providers/twitter"
25+
import WorkOS from "@auth/fastify/providers/workos"
26+
import Zoom from "@auth/fastify/providers/zoom"
27+
28+
export const authConfig = {
29+
trustHost: true,
30+
debug: process.env.NODE_ENV !== "production",
31+
providers: [
32+
Apple,
33+
Auth0,
34+
AzureB2C({
35+
clientId: process.env.AUTH_AZURE_AD_B2C_ID,
36+
clientSecret: process.env.AUTH_AZURE_AD_B2C_SECRET,
37+
issuer: process.env.AUTH_AZURE_AD_B2C_ISSUER,
38+
}),
39+
BoxyHQSAML({
40+
clientId: "dummy",
41+
clientSecret: "dummy",
42+
issuer: process.env.AUTH_BOXYHQ_SAML_ISSUER,
43+
}),
44+
Cognito,
45+
Coinbase,
46+
Discord,
47+
Dropbox,
48+
Facebook,
49+
GitHub,
50+
Gitlab,
51+
Google,
52+
Hubspot,
53+
Keycloak,
54+
LinkedIn,
55+
Netlify,
56+
Okta,
57+
Passage,
58+
Pinterest,
59+
Reddit,
60+
Slack,
61+
Spotify,
62+
Twitch,
63+
Twitter,
64+
WorkOS({
65+
connection: process.env.AUTH_WORKOS_CONNECTION!,
66+
}),
67+
Zoom,
68+
],
69+
}

Diff for: apps/dev/fastify/src/errors.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export class HttpError extends Error {
2+
status: number
3+
constructor(status: number, message: string) {
4+
super(message)
5+
this.status = status
6+
}
7+
}
8+
9+
export class NotFoundError extends HttpError {
10+
constructor(message: string, status = 404) {
11+
super(status, message)
12+
this.name = "NotFoundError"
13+
}
14+
}

Diff for: apps/dev/fastify/src/middleware/auth.middleware.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// @ts-nocheck
2+
import { getSession } from "@auth/fastify"
3+
import { authConfig } from "../config/auth.config.js"
4+
import { FastifyReply, FastifyRequest } from "fastify"
5+
6+
export async function authenticatedApi(
7+
req: FastifyRequest,
8+
reply: FastifyReply
9+
) {
10+
reply.session ??= (await getSession(req, authConfig)) ?? null
11+
if (!reply.session) {
12+
reply.status(401).send({ message: "Not Authenticated" })
13+
}
14+
}
15+
16+
export async function authenticatedPage(
17+
req: FastifyRequest,
18+
reply: FastifyReply
19+
) {
20+
reply.session ??= (await getSession(req, authConfig)) ?? null
21+
if (!reply.session) {
22+
return reply.view("unauthenticated")
23+
}
24+
}
25+
26+
export async function currentSession(req: FastifyRequest, reply: FastifyReply) {
27+
reply.session = (await getSession(req, authConfig)) ?? null
28+
}

Diff for: apps/dev/fastify/src/middleware/error.middleware.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//// @ts-nocheck
2+
import { HttpError, NotFoundError } from "../errors.js"
3+
import { FastifyReply, FastifyRequest } from "fastify"
4+
5+
export const errorHandler = (
6+
err: HttpError | Error,
7+
_req: FastifyRequest,
8+
_reply: FastifyReply
9+
): void => {
10+
const statusCode = (err instanceof HttpError && err.status) || 500
11+
_reply.status(statusCode).view("error", {
12+
title: err instanceof HttpError ? err.status : err.name,
13+
message: err.message,
14+
})
15+
}
16+
17+
export const errorNotFoundHandler = (
18+
_req: FastifyRequest,
19+
_reply: FastifyReply
20+
): void => {
21+
new NotFoundError("Not Found")
22+
}

Diff for: apps/dev/fastify/src/server.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const { fastify } = await import("./app.js")
2+
3+
// const address = fastify.server.address()
4+
// const port = (typeof address === "object" && address?.port) || 3000
5+
6+
const port = Number(process.env.PORT) || 3000
7+
8+
const start = async () => {
9+
try {
10+
await fastify.listen({ port })
11+
fastify.log.info(`server listening on ${fastify.server.address()}`)
12+
} catch (err) {
13+
fastify.log.error(err)
14+
process.exit(1)
15+
}
16+
}
17+
18+
start()

Diff for: apps/dev/fastify/tsconfig.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"compilerOptions": {
3+
"module": "NodeNext",
4+
"esModuleInterop": true,
5+
"target": "esnext",
6+
"noImplicitAny": true,
7+
"moduleResolution": "NodeNext",
8+
"sourceMap": true,
9+
"outDir": "dist",
10+
"baseUrl": ".",
11+
"skipLibCheck": true,
12+
"strict": true
13+
},
14+
"include": ["src/**/*.ts"],
15+
"exclude": ["node_modules"]
16+
}

Diff for: apps/dev/fastify/views/error.pug

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extends layout
2+
3+
block content
4+
h1(class="text-3xl font-bold")= title
5+
p= message

Diff for: apps/dev/fastify/views/index.pug

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
extends layout
2+
3+
block content
4+
h1(class="text-3xl font-bold")= title
5+
p
6+
| This is an example site to demonstrate how to use #{ ' ' }
7+
a(href="https://fastify.dev/", class="mb-2 font-medium underline") Fastify
8+
| #{ ' ' } with #{ ' ' }
9+
a(href="https://authjs.dev/reference/fastify", class="underline") Fastify Auth
10+
|
11+
| for authentication.

0 commit comments

Comments
 (0)