Created
April 14, 2025 07:19
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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