alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
150 lines (149 loc) • 4.96 kB
JavaScript
import { $hook, $inject, $module, Alepha } from "alepha";
import { AlephaReact, useAlepha, useStore } from "alepha/react";
import { $auth, AlephaServerAuth, alephaServerAuthRoutes, tokenResponseSchema, userinfoResponseSchema } from "alepha/server/auth";
import { AlephaServerLinks, LinkProvider } from "alepha/server/links";
import { currentUserAtom } from "alepha/security";
import { $logger } from "alepha/logger";
import { ReactBrowserProvider, Redirection } from "alepha/react/router";
import { HttpClient } from "alepha/server";
//#region ../../src/react/auth/providers/ReactAuthProvider.ts
var ReactAuthProvider = class {
alepha = $inject(Alepha);
onRender = $hook({
on: "react:server:render:begin",
handler: async ({ request, state }) => {
if (request?.user) {
const { token, realm, ...user } = request.user;
this.alepha.store.set(currentUserAtom, user);
state.user = user;
}
}
});
};
//#endregion
//#region ../../src/react/auth/services/ReactAuth.ts
/**
* Browser, SSR friendly, service to handle authentication.
*/
var ReactAuth = class {
log = $logger();
alepha = $inject(Alepha);
httpClient = $inject(HttpClient);
linkProvider = $inject(LinkProvider);
onBeginTransition = $hook({
on: "react:transition:begin",
handler: async (event) => {
if (this.alepha.isBrowser()) Object.defineProperty(event.state, "user", { get: () => this.user });
}
});
onFetchRequest = $hook({
on: "client:onRequest",
handler: async ({ request }) => {
if (this.alepha.isBrowser() && this.user) request.credentials ??= "include";
}
});
/**
* Get the current authenticated user.
*
* Alias for `alepha.state.get("user")`
*/
get user() {
return this.alepha.store.get(currentUserAtom);
}
async ping() {
const { data } = await this.httpClient.fetch(alephaServerAuthRoutes.userinfo, { schema: { response: userinfoResponseSchema } });
this.alepha.store.set("alepha.server.request.apiLinks", data.api);
this.alepha.store.set(currentUserAtom, data.user);
return data.user;
}
can(action) {
if (!this.user) return false;
return this.linkProvider.can(action);
}
async login(provider, options) {
const realmParam = options.realm ? `&realm=${encodeURIComponent(options.realm)}` : "";
if (options.username || options.password) {
const { data } = await this.httpClient.fetch(`${options.hostname || ""}${alephaServerAuthRoutes.token}?provider=${provider}${realmParam}`, {
method: "POST",
body: JSON.stringify({
username: options.username,
password: options.password
}),
schema: { response: tokenResponseSchema }
});
this.alepha.store.set("alepha.server.request.apiLinks", data.api);
this.alepha.store.set(currentUserAtom, data.user);
return data;
}
if (this.alepha.isBrowser()) {
const browser = this.alepha.inject(ReactBrowserProvider);
const redirect = options.redirect || (browser.transitioning ? window.location.origin + browser.transitioning.to : window.location.href);
const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${encodeURIComponent(redirect)}`;
if (browser.transitioning) throw new Redirection(href);
else {
window.location.href = href;
return {};
}
}
throw new Redirection(`${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || "/"}`);
}
logout() {
const form = document.createElement("form");
form.method = "POST";
form.action = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;
form.style.display = "none";
document.body.appendChild(form);
form.submit();
}
};
//#endregion
//#region ../../src/react/auth/hooks/useAuth.ts
const useAuth = () => {
const alepha = useAlepha();
const [user] = useStore(currentUserAtom);
return {
user,
logout: () => {
alepha.inject(ReactAuth).logout();
},
login: async (provider, options = {}) => {
await alepha.inject(ReactAuth).login(provider, options);
},
/**
* UI permission check — does the current user hold this permission?
* Supports exact and wildcard names (e.g. `"admin:*"`, `"admin:user:read"`).
*
* UI affordance only (show/hide/disable). Real access control is enforced
* server-side via `$secure` on the route/action.
*/
has: (permission) => {
return alepha.inject(LinkProvider).can(permission);
}
};
};
//#endregion
//#region ../../src/react/auth/index.ts
/**
* Auth-related React components and hooks.
*
* **Features:**
* - Login/logout components
* - Protected route wrappers
* - Auth state hooks
*
* @module alepha.react.auth
*/
const AlephaReactAuth = $module({
name: "alepha.react.auth",
primitives: [$auth],
services: [
AlephaReact,
AlephaServerLinks,
AlephaServerAuth,
ReactAuthProvider,
ReactAuth
]
});
//#endregion
export { AlephaReactAuth, ReactAuth, ReactAuthProvider, useAuth };
//# sourceMappingURL=index.js.map