npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@farbenmeer/auth

v3.0.0

Published

This is a simple client library for farbenmeer's central authentication and authorization service [auth.farbenmeer.de](https://auth.farbenmeer.de).

Downloads

1,328

Readme

@farbenmeer/auth

This is a simple client library for farbenmeer's central authentication and authorization service auth.farbenmeer.de.

Usage

Add to your project using

pnpm add @farbenmeer/auth

Set up your middleware:

// middleware.ts

export { defaultMiddleware as default } from "@farbenmeer/auth"

or

// middleware.ts

import { withAuth } from "@farbenmeer/auth"
import { NextRequest, NextResponse } from "next/server"

export default withAuth(async (req: NextRequest) => {
    // your middleware here.
})

Set up callback page. This needs to be located at app/api/auth/callback.tsx. It will render all visible output from @farbenmeer/auth

// api/auth/callback.tsx
import { CallbackPage } from "@farbenmeer/auth"

export default function Page({ searchParams }: { searchParams: Record<string, string> }) {
    return <CallbackPage searchParams={searchParams} />
}

Environment

For the farbenmeer auth client to work you need to set some specific environment variables. Visit auth.farbenmeer.de/clients and create your app and a new client for this app. Copy the client ID and client secret to the environment variables

FM_AUTH_CLIENT_ID
FM_AUTH_CLIENT_SECRET

of your application.

You also need to set the variable BASE_URL to the URL under which your app will be publicly available. For Vercel Previews this will fall back to VERCEL_URL but for the production deployment, you must set the BASE_URL.

Preview Deployments

If you are using preview deployments you need to register your redirect URI with the auth service so you can log in on preview deployments. This can be done in a github actions pipeline:

# .github/workflows/preview.yaml
# with all necessary environment variables set

run: pnpm registerRedirectURI

or during your build step:

{
    "scripts": {
        "build": "registerRedirectURI && next build"
    }
}

Customizing the Auth Screen

The CallbackPage component is rendered for

  • the loading state while the user is being redirected during the auth flow
  • various authentication error states

it returns some markup, usually a heading, a paragraph with an error description, and some anchors for skipping the automatic redirect timeout and retrying authentication. To customize the look, wrap it in some more markup:

export default function Page({ searchParams }: { searchParams: Record<string, string> }) {
    return (
        <main className="bg-white rounded-md p-8 [&_a]:underline">
            <CallbackPage searchParams={searchParams} />
        </main>
    )
}

To customize the messages shown on specific events, pass a messages object

export default function Page({ searchParams }: { searchParams: Record<string, string> }) {
    return <CallbackPage searchParams={searchParams} messages={{ redirecting: "Sie werden weitergeleitet..." }} />
}

all messages are defined in the typescript type for the CallbackPage component.

Customer CI (Coroporate Identity)

If your client requires the auth server to use a custom CI for the login and logout pages and the custom CI components have been setup on the server (as a separate folder in app/) you can configute the variable FM_AUTH_CI with the name of your custom CI folder to redirect all sign-in/sign-out requests.

Testing

If you need to run tests or other tools agains an app that uses this package you can use a CI (Continuous Integration) Token. Yes, this naming is ambiguous with CI as in corporate identity, sorry. To do so, go to auth.farbenmeer.de (or the server instance you are using), login with your farbenmeer account, go to the admin interface to the users-tab and set up the user you want to use for the tests.

Then create a CI Token for that user. When running requests, pass that along in the header x-fm-auth-ci-token. Your test browser will be logged in as the user for which you created the CI token.

This will not work in production which is detected using the VERCEL_ENV environment variable so on instances with VERCEL_ENV==="production" you cannot use CI-Tokens. Tests should be executed against preview or staging environments and use a separate fm-auth app from the production app so you don't mess with prod user data.

API

All routes handled by the auth middleware will only be available to logged in users. Within such authenticated routes, in any server component, route or server action, you can now use the following APIs:

The Sign-Out button

To render a valid sign-out button you will need to set up a server action that calls signOut and redirects to the returned URL:

"use server"
import { signOut } from "@farbenmeer/auth"
import { redirect } from "next/navigation"

export async function signOutAction() {
    redirect(await signOut())
}

For a complete logout button something like

<form action={signOutAction}>
    <button>Sign Out</button>
</form>

will work.

Sign-Out flow using an API route

If it is necessary to support pages that use the legacy pages-router, you need to create an API Route for the sign out flow instead of a server action.

For the action (e.G. in /app/api/sign-out/route.ts) the following will be sufficient:

import { signOutResponse } from "@farbenmeer/auth";

export function GET() {
  return signOutResponse();
}

and link to it via

<a href="/api/sign-out">Sign Out</a>

The User-Type

All following APIs are centered around the User-type

interface User {
    id: number;
    email: string;
    fullName: string | null;
    image: string | null;
    roles: string[];
    blocked: boolean;
}

getUser

Gets the currently logged-in user.

import { getUser } from "@farbenmeer/auth"

const user = await getUser()

console.log(user.fullName, user.email)

hasRole

Checks if the user has a specific role. If no user is provided it will fall back to getUser to get the currently logged-in user. This behaviour is particularly userful if you need to check the same user for multiple roles or other properties.

import { hasRole } from "@farbenmeer/auth"

const admin = await hasRole("Admin")

or

import { getUser, hasRole } from "@farbenmeer/auth"

const user = await getUser()
const admin = hasRole("Admin", user)

enforceRole

Checks if the user has a specific role and throws an error if they do not.

import { enforceRole } from "@farbenmeer/auth"

await enforceRole("Admin")

or

import { getUser, enforceRole } from "@farbenmeer/auth"

const user = await getUser()
enforceRole("Admin", user)

getUsers

Get all users of your app:

import { getUsers } from "@farbenmeer/auth"

const users = await getUsers()

Get users with specific roles:

import { getUsers } from "@farbenmeer/auth"

const users = await getUsers({ roles: ["Admin", "Editor"] })

Get users for a list of email addresses:

import { getUsers } from "@farbenmeer/auth"

const users = await getUsers({ emails: ["[email protected]", "[email protected]"] })

getUserByEmail

Get a specific user by their email address

import { getUserByEmail } from "@farbenmeer/auth"

const fred = await getUserByEmail("[email protected]")

bulkUpdateUsers

Update a bulk of users to match the objects passed to the method. This can update the fullName, email, image and roles of each user.

import { bulkUpdateUsers } from "@farbenmeer/auth"

await bulkUpdateUsers([
    { id: 1, fullName: "Fred Farbenmeer", email: "[email protected]", image: null, roles: ["Admin"] }
])

Domain

A domain means that users with an email address ending in this domain will automatically be able to log in to the app as a Viewer.

export interface Domain {
  appId: string;
  appName: string;
  domain: string;
}

getDomains

Get a list of domains registered for current app.

import { getDomains } from "@farbenmeer/auth"

const domains = await getDomains()

putDomain

Register a domain for the current app.

import { putDomain } from "@farbenmeer/auth"

await putDomain()

deleteDomain

Remove a domain from the current app. New users with this domain will not be able to log in automatically while users that have logged in before will still be able to log in.

import { deleteDomain } from "@farbenmeer/auth"

await deleteDomain()

getExternalToken

Get an access token for a specific external auth provider.

import { getExternalToken } from "@farbenmeer/auth"

await getExternalToken("...")

providers and their respective scopes must be configures on the server.

usage in API Routes

To use the aforementioned APIs in API Routes, the request object must be explicitly passed to each function, e.G.

import { getUser } from "@farbenmeer/auth"

export function GET(req: NextRequest) {
    const user = await getUser({ req })
    const fred = await getUserByEmail("[email protected]", { req })
}

Migration guide v2 -> v3

The signOut function is now async.

/// before
import { signOut } from "@farbenmeer/auth"

signOut()
/// after
import { signOut } from "@farbenmeer/auth"

await signOut()