GithubHelp home page GithubHelp logo

luccasr73 / oslo Goto Github PK

View Code? Open in Web Editor NEW

This project forked from pilcrowonpaper/oslo

0.0 0.0 0.0 68 KB

A collection of auth-related utilities

License: MIT License

JavaScript 1.30% TypeScript 98.70%

oslo's Introduction

oslo

This package is highly experimental - use at your own risk

A collection of utilities for auth, including:

Aside from oslo/password, every module works in any environment, including Node.js, Cloudflare Workers, Deno, and Bun.

Installation

npm i oslo
pnpm add oslo
yarn add oslo

oslo/cookie

import { serializeCookie } from "oslo/cookie";

const cookie = serializeCookie(name, value, {
	// optional
	expires: new Date(),
	maxAge: 60 * 60,
	path: "/",
	httpOnly: true,
	secure: true,
	sameSite: "lax"
});
response.headers.set("Set-Cookie", cookie);

// names and values are URI component encoded
serializeCookie("this is fine =;", "this too =;");
import { parseCookieHeader } from "oslo/cookie";

const cookies = parseCookieHeader("cookie1=hello; cookie2=bye");
const cookie1 = cookies.get("cookie1"); // string | null
const entries = cookies.entries();

oslo/encoding

import { encodeBase64, decodeBase64 } from "oslo/encoding";

const textEncoder = new TextEncoder();
const encoded = encodeBase64(textEncoder.encode("hello world"));
const decoded = decodeBase64(encoded);

import { encodeBase64url, decodeBase64url } from "oslo/encoding";
import { encodeHex, decodeHex } from "oslo/encoding";
import { encodeBase32, decodeBase32 } from "oslo/encoding";

oslo/oauth2

import { createAuthorizationURL } from "oslo/oauth2";

const [url, state] = await createAuthorizationURL(
	"https://github.com/login/oauth/authorize",
	{
		clientId,
		redirectURI,
		scope: ["user:email"]
	}
);

// see also `oslo/cookie`
setCookie("github_oauth_state", state, {
	httpOnly: true,
	path: "/",
	maxAge: 60 * 60, // 1 hour
	secure: true
});

redirect(url);
import { createAuthorizationURLWithPKCE } from "oslo/oauth2";

const [url, codeVerifier, state] = await createAuthorizationURLWithPKCE();

// store `codeVerifier` as cookie
import {
	verifyState,
	validateAuthorizationCode,
	AccessTokenRequestError
} from "oslo/oauth2";

const storedState = getCookie("github_oauth_state");
const state = url.searchParams.get("state");
if (!verifyState(storedState, state)) {
	// error
}
const code = url.searchParams.get("code");
if (!code) {
	// error
}

try {
	const { accessToken, refreshToken } = await validateAuthorizationCode<{
		refreshToken: string;
	}>(code, {
		tokenEndpoint: "https://github.com/login/oauth/access_token",
		clientId: this.options.clientId,
		clientPassword: {
			clientSecret: this.options.clientSecret,
			authenticateWith: "client_secret"
		}
	});
} catch (e) {
	if (e instanceof AccessTokenRequestError) {
		// see https://www.rfc-editor.org/rfc/rfc6749#section-5.2
		const { request, message, description } = e;
	}
	// unknown error
}

oslo/oauth2/providers

import { Github, Apple, Google } from "oslo/oauth2/providers";

const github = new Github({
	clientId,
	clientSecret,
	scope: ["user:email"]
});

// wrapper around `createAuthorizationURL()`
const [url, state] = await github.createAuthorizationURL();

// wrapper around `validateAuthorizationCode()`
const tokens = await github.validateAuthorizationCode(code);

oslo/oidc

import { parseIdToken } from "oslo/oidc";

const { sub, exp, email } = parseIdToken<{ email: string }>(idToken);

oslo/otp

import { generateHOTP } from "oslo/otp";

const secret = new Uint8Array(20);
crypto.getRandomValues(secret);

let counter = 0;

const otp = await generateHOTP(secret, counter); // default 6 digits
const otp = await generateHOTP(secret, counter, 8); // 8 digits (max)
import { TOTPController } from "oslo/otp";
import { TimeSpan } from "oslo";

const totpController = new TOTPController({
	// optional
	period: new TimeSpan(30, "s"), // default: 30s
	digits: 6 // default: 6
});

const secret = new Uint8Array(20);
crypto.getRandomValues(secret);

const otp = await totpController.generate(secret);
const validOTP = await totpController.verify(otp, secret);
import { createKeyURI } from "oslo/otp";

const secret = new Uint8Array(20);
crypto.getRandomValues(secret);

const uri = createKeyURI({
	type: "totp",
	secret,
	issuer: "My website",
	accountName: "[email protected]",
	//optional
	period: new TimeSpan(30, "s"), // default: 30s
	algorithm: "SHA-1", // ignored by google authenticator
	digits: 6 // default: 6
});
const uri = createKeyURI({
	type: "hotp",
	secret,
	issuer: "My website",
	accountName: "[email protected]",
	//optional
	counter, //default: 0
	digits: 6 // default: 6
});

const qr = createQRCode(uri); // example

oslo/password

Hash passwords with argon2id, scrypt, and bcrypt using the fastest package available for Node.js.

import { Argon2id, Scrypt, Bcrypt } from "oslo/password";

// `Scrypt` and `Bcrypt` implement the same methods
const argon2id = new Argon2id(options);
const hash = await argon2id.hash(password);
const matches = await argon2id.verify(hash, password);

This specific module only works in Node.js. See these packages for other runtimes:

oslo/random

All functions are cryptographically secure.

import { generateRandomString, alphabet } from "oslo/random";

const id = generateRandomString(16, alphabet("0-9", "a-z", "A-Z")); // alphanumeric
const id = generateRandomString(16, alphabet("0-9", "a-z")); // alphanumeric (lowercase)
const id = generateRandomString(16, alphabet("0-9")); // numbers only
const id = generateRandomString(16, alphabet("0-9", "a-z", "A-Z", "-", "-")); // alphanumeric with `_` and`-`
import { random } from "oslo/random";

const num = random(); // cryptographically secure alternative to `Math.random()`
import { generateRandomNumber } from "oslo/random";

// random integer between 0 (inclusive) and 10 (exclusive)
const num = generateRandomNumber(0, 10);

oslo/request

CSRF protection.

import { verifyRequestOrigin } from "oslo/request";

// only allow same-origin requests
const validRequestOrigin = verifyRequestOrigin(request.headers.get("Origin"), {
	host: request.headers.get("Host")
});
const validRequestOrigin = verifyRequestOrigin(request.headers.get("Origin"), {
	host: request.url
});

if (!validRequestOrigin) {
	// invalid request origin
	return new Response(null, {
		status: 400
	});
}
// true
verifyRequestOrigin("https://example.com", {
	host: "example.com"
});

// true
verifyRequestOrigin("https://foo.example.com", {
	host: "bar.example.com",
	allowedSubdomains: "*" // wild card to allow any subdomains
});

// true
verifyRequestOrigin("https://foo.example.com", {
	host: "bar.example.com",
	allowedSubdomains: ["foo"]
});

// true
verifyRequestOrigin("https://example.com", {
	host: "foo.example.com",
	allowedSubdomains: [null] // `null` to only allow base domain
});

oslo/session

import { SessionController } from "oslo/session";
import { generateRandomString, alphabet } from "oslo/random";
import { TimeSpan } from "oslo";

import type { Session } from "oslo/session";

// implements sliding window expiration
// expires in 30 days unless used within 15 days before expiration (1/2 of expiration)
// in which case the expiration gets pushed back another 30 days
const sessionController = new SessionController(new TimeSpan(30, "d"));

async function validateSession(sessionId: string): Promise<Session | null> {
	const databaseSession = await db.getSession(sessionId);
	if (!databaseSession) {
		return null;
	}
	const session = sessionController.validateSessionState(
		sessionId,
		databaseSession.expires
	);
	if (!session) {
		await db.deleteSession(sessionId);
		return null;
	}
	if (session.fresh) {
		// session expiration was extended
		await db.updateSession(session.sessionId, {
			expires: session.expiresAt
		});
	}
	return session;
}

async function createSession(): Promise<Session> {
	const sessionId = generateRandomString(41, alphabet("a-z", "A-Z", "0-9"));
	const session = sessionController.createSession(sessionId);
	await db.insertSession({
		// you can store any data you want :D
		id: session.sessionId,
		expires: session.expiresAt
	});
	return session;
}

const sessionCookieController = sessionController.sessionCookieController({
	name: "session",
	secure: prod,
	secret
});
const session = await createSession();
// store session
const cookie = sessionCookieController.createSessionCookie(session.sessionId);
// get cookie
const sessionId = sessionCookieController.parseCookieHeader(
	headers.get("Cookie")
);
const sessionId = cookies.get(sessionCookieController.cookieName);

if (!sessionId) {
	// 401
}
const session = await validateSession(sessionId);
if (session.fresh) {
	// session expiration was extended
	const cookie = sessionCookieController.createSessionCookie(session.sessionId);
	// set cookie again
}
// delete session by overriding the current one
const cookie = sessionCookieController.createBlankSessionCookie();

oslo/token

For email verification tokens and password reset tokens.

import { VerificationTokenController } from "oslo/token";
import { isWithinExpirationDate } from "oslo";
import { generateRandomString, alphabet } from "oslo/random";

import type { Token } from "oslo/token";

// expires in 2 hours
const verificationTokenController = new VerificationTokenController(
	new TimeSpan(2, "h")
);

async function generatePasswordResetToken(userId: string): Promise<Token> {
	// check if an unused token already exists
	const storedUserTokens = await db
		.table("password_reset_token")
		.where("user_id", "=", userId)
		.getAll();
	if (storedUserTokens.length > 0) {
		// if exists, check the expiration
		const reusableStoredToken = storedUserTokens.find((token) => {
			// returns true if there's 1 hour left (1/2 of 2 hours)
			return verificationTokenController.isTokenReusable(token.expires);
		});
		// reuse token if it exists
		if (reusableStoredToken) return reusableStoredToken.id;
	}
	// generate a new token and store it
	const token = verificationTokenController.createToken(
		generateRandomString(63, alphabet("a-z", "0-9")),
		userId
	);
	await db
		.insertInto("password_reset_token")
		.values({
			id: token.value,
			expires: token.expiresAt,
			user_id: token.userId
		})
		.executeTakeFirst();
	return token;
}

async function validatePasswordResetToken(token: string): Promise<string> {
	const storedToken = await db.transaction().execute(async (trx) => {
		// get token from db
		const storedToken = await trx
			.table("password_reset_token")
			.where("id", "=", token)
			.get();
		if (!storedToken) return null;
		await trx.table("password_reset_token").where("id", "=", token).delete();
		return storedToken;
	});
	if (!storedToken) throw new Error("Invalid token");
	// check for expiration
	if (!isWithinExpirationDate(storedToken.expires)) {
		throw new Error("Expired token");
	}
	// return owner
	return storedToken.user_id;
}
const token = await generatePasswordResetToken(session.userId);
// send password reset email with link (page with form for inputting new password)
await sendEmail(`http://localhost:3000/reset-password/${token.value}`);

oslo/webauthn

validateAttestationResponse() does not validate attestation certificates.

import { validateAttestationResponse } from "oslo/webauthn";

import type { AttestationResponse } from "oslo/webauthn";

try {
	const response: AttestationResponse = {
		// all `ArrayBufferLike` type (`Uint8Array`, `ArrayBuffer` etc)
		clientDataJSON,
		authenticatorData
	};
	await validateAttestationResponse(response, {
		challenge, //  `ArrayBufferLike`
		origin: "http://localhost:3000" // website origin
	});
} catch {
	// failed to validate attestation response
}

validateAssertionResponse() currently only supports ECDSA using secp256k1 curve and SHA-256 (algorithm ID -7).

import { validateAssertionResponse } from "oslo/webauthn";

import type { AssertionResponse } from "oslo/webauthn";

try {
	const response: AssertionResponse = {
		// all `ArrayBufferLike` type (`Uint8Array`, `ArrayBuffer` etc)
		clientDataJSON,
		authenticatorData,
		signature
	};
	await validateAssertionResponse(response, {
		algorithm: "ES256K",
		challenge, // `ArrayBufferLike`
		publicKey, // `ArrayBufferLike`
		origin: "http://localhost:3000" // website origin
	});
} catch {
	// failed to validate assertion response
}

oslo's People

Contributors

pilcrowonpaper avatar pawelblaszczyk5 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.