npm i @rocketbase/skeleton-key
// auth.ts
import {SkeletonKey} from "@rocketbase/skeleton-key";
export const auth = new SkeletonKey();
// api.ts
import {auth} from "./auth";
import axios from "axios";
export async function getData() {
if (!auth.isLoggedIn())
throw new Error("Need to be logged in!");
// Auth headers are appended automatically.
return axios.get('/the/data');
}
export async function getOtherData() {
// Auth headers are not appended unless this app is hosted on example.com
return axios.get('https://example.com/other/data');
}
export function getUserId() {
// Data encoded in the jwt is accessible through tokenData and refreshTokenData getter.
return auth.tokenData.payload.customerId;
}
export function getUserAttributes() {
// Data returned as the user response is accessible through the userData getter.
return auth.userData;
}
// auth.ts
import {SkeletonKey} from "@rocketbase/skeleton-key";
interface UserExtensions {
customAttributes: string;
goHere: boolean;
forTypingAssist: any[]
}
interface JwtExtensions {
tokenExtensions: string;
goHere: number;
forTypingAssist: any
}
export const auth = new SkeletonKey<UserExtensions, JwtExtensions>({
domains: ['*'], // Will insert auth headers for every domain
url: "https://the.auth.server/path", // Path to the auth server to run requests against
intercept: true, // Automatically intercept all requests and insert headers where necessary
renewType: "action", // Automatically refresh token once it expires
authHeader: "Authorization", // The header to inject the token under
authPrefix: "Bearer ", // Prefix of the header value
authSuffix: "", // Suffix of the header value
storageKey: "io.rocketbase.commons.auth" // The key in localStorage the token and user data is persisted under
});
// decorators.ts
import {auth} from "./auth";
import {LoginDialog} from "./some/LoginDialog";
/**
* Checks if a user is logged in.
* If not, delays execution until successful login and trigger a login dialog
*/
export function NeedsLogin(target: any, propertyKey: string | symbol, desc: PropertyDescriptor) {
const {value} = desc;
desc.value = async function(this: any, ...args: any[]) {
if (!auth.isLoggedIn()) {
LoginDialog.open();
await auth.waitForLogin();
}
return value.apply(this, args);
};
return desc;
}
/**
* Checks if a user has a given role.
* If not, prevents execution and throws an error.
*/
export function NeedsRole(role: string) {
return function(target: any, propertyKey: string | symbol, desc: PropertyDescriptor) {
const {value} = desc;
desc.value = function(this: any, ...args: any[]) {
if (!auth.isLoggedIn() || !auth.userData.roles.contains(role))
throw new Error(`User needs to be member of role ${role}!`);
return value.apply(this, args);
}
return desc;
}
}
// api.ts
import {NeedsLogin, NeedsRole} from "./decorators";
import axios from "axios";
export class Api {
@NeedsLogin
async getSomeData(id: string) {
return axios.get(`/path/to/service/data/${id}`);
}
@NeedsRole("ADMIN")
async getUserData(id: string) {
return axios.get(`/path/to/service/users/${id}`);
}
// Can also be combined
@NeedsLogin
@NeedsRole("ADMIN")
async setUserPassword(id: string, password: string) {
return axios.put(`/path/to/service/users/${id}`, JSON.stringify({password}), {headers:{'Content-Type': 'application/json'}});
}
}