Skip to content

Instantly share code, notes, and snippets.

@whitetigle
Created April 14, 2025 07:19
Show Gist options
  • Save whitetigle/017265b22c172027a19e37646e1a49c1 to your computer and use it in GitHub Desktop.
Save whitetigle/017265b22c172027a19e37646e1a49c1 to your computer and use it in GitHub Desktop.
A super lite plugin to work with Pouchdb and manage authentication + cookies. Based on former pouchdb-authentication plugin
class AuthError extends Error {
constructor(message) {
super(message);
this.status = 400;
this.name = "authentication_error";
this.message = message;
this.error = true;
// Maintains proper stack trace (only available in V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, AuthError);
}
}
}
function getSessionPath(db) {
const sessionPath = "/_session";
return db.name + sessionPath;
}
function encodeBasicAuth(username, password) {
const credentials = `${username}:${password}`;
const utf8 = new TextEncoder().encode(credentials);
const base64 = btoa(String.fromCharCode(...utf8));
return { Authorization: `Basic ${base64}` };
}
function getBasicAuthHeaders(db) {
let auth = null;
const url = db.name.substring(0, db.name.indexOf("?"));
const urlParts = url.split(":");
if (urlParts.length > 1) {
auth = {
username: urlParts[1],
password: urlParts[2],
};
}
if (!auth) {
return {};
}
return {
Authorization: "Basic " + encodeBasicAuth(auth.username, auth.password),
};
}
// Wrapper around the fetch API to send and receive JSON objects
async function fetchJSON(db, path, options = {}) {
function wrapError(err) {
if (err.name === "unknown_error") {
err.message =
(err.message || "") +
" Unknown error! Did you remember to enable CORS?";
}
throw err;
}
const authHeaders = getBasicAuthHeaders(db);
options.headers = Object.assign({}, authHeaders, options.headers || {});
options.headers["Content-Type"] = "application/json";
options.headers["Accept"] = "application/json";
options.credentials = "include"; // include cookies in the request
return fetch(path, options)
.then(async (response) => {
if (response.ok) {
const json = await response.json();
// console.log(`json=${JSON.stringify(json)}`);
return json;
} else {
return response.json().then((responseError) => {
throw responseError;
});
}
})
.catch(wrapError);
}
// toCallback takes a function that returns a promise,
// and returns a function that can be used with a callback as the last argument,
// but still retains the ability to be used without a callback and return a promise.
function toCallback(func) {
return function (...args) {
const lastArg = args[args.length - 1];
if (typeof lastArg === "function") {
const callback = lastArg;
// Remove the callback from args before passing to func
return func.apply(this, args.slice(0, -1)).then(
(res) => callback(null, res),
(err) => callback(err)
);
} else {
return func.apply(this, args);
}
};
}
// Common function to handle authentication operations
function createAuthFunction(method, requiresCredentials = false) {
return toCallback(function (username, password, opts) {
// Handle different parameter patterns
let authOpts;
if (method !== "POST") {
// For non-login operations like logout and getSession
authOpts = username;
if (typeof authOpts === "undefined") {
authOpts = {};
}
} else {
// For login operations
if (typeof opts === "undefined") {
opts = {};
}
if (requiresCredentials) {
// Validate credentials for login
if (!username) {
return Promise.reject(new AuthError("you must provide a username"));
} else if (!password) {
return Promise.reject(new AuthError("you must provide a password"));
}
}
authOpts = opts;
}
const db = this;
// Check adapter type for all operations
if (["http", "https"].indexOf(db.type()) === -1) {
return Promise.reject(
new AuthError("this plugin only works for the http/https adapter")
);
}
const path = getSessionPath(db);
const requestOpts = Object.assign(
{ method },
method === "POST" ? { body: { name: username, password: password } } : {},
authOpts.ajax || {}
);
return fetchJSON(db, path, requestOpts);
});
}
const logIn = createAuthFunction("POST", true);
const logOut = createAuthFunction("DELETE");
const getSession = createAuthFunction("GET");
const plugin = {};
plugin.name = "pouchdb.lite.authentication";
plugin.version = "1.0.0";
plugin.description = "PouchDB lite plugin for authentication";
plugin.login = logIn;
plugin.logIn = logIn;
plugin.logout = logOut;
plugin.logOut = logOut;
plugin.getSession = getSession;
if (typeof window !== "undefined" && window.PouchDB) {
window.PouchDB.plugin(plugin);
console.log(
`PouchDB Authentication Plugin loaded. Version: ${plugin.version}.`
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment