@upvave/hds-auth
v1.1.4
Published
TypeScript auth SDK for connecting Hairdressing School apps to the central HDS Auth Server.
Readme
@upvave/hds-auth
Official TypeScript SDK for connecting Hairdressing School applications to the central HDS Auth Server.
The SDK gives each app a stable auth API while keeping Better Auth and server internals behind the central auth service.
App
-> @upvave/hds-auth
-> Central HDS Auth Server
-> Better AuthFeatures
- Browser login, logout, session refresh, silent auth, SSO exchange, invite, and password flows.
- React session provider and hooks.
- Server-side signed requests with
HDS_APP_SECRET. - User, role, permission, invite, password, and app profile administration APIs.
- Next.js middleware, route handler, server action, role guard, and permission guard helpers.
- Clean
HdsAuthErrorvalues instead of raw auth server errors. - ESM-only TypeScript package with generated declarations.
Installation
bun add @upvave/hds-authnpm install @upvave/hds-authFor production apps, pin the SDK version explicitly:
{
"dependencies": {
"@upvave/hds-auth": "1.0.0"
}
}Requirements
- Node.js
>=20.19.0, Bun>=1.3.0, or a compatible Next.js runtime. - A central HDS Auth Server reachable over HTTPS.
- React
>=18.2.0only when using the React hooks/provider.
Package Entrypoints
import { createAuthClient } from '@upvave/hds-auth/client';
import { createAuthServerClient } from '@upvave/hds-auth/server';
import { createNextAuth } from '@upvave/hds-auth/next';Published subpaths:
@upvave/hds-auth@upvave/hds-auth/client@upvave/hds-auth/server@upvave/hds-auth/next@upvave/hds-auth/types@upvave/hds-auth/errors@upvave/hds-auth/utils
Environment
Server-side apps should define:
HDS_AUTH_SERVER_URL=https://auth.example.com
HDS_APP_ID=salma
HDS_APP_SECRET=xxxxHDS_APP_SECRET is server-only. Never pass it to browser code, React props, client components, public env variables, or bundled frontend code.
Browser Client
Use the client entrypoint in browser code. It requires only the public auth server URL and app id.
import { createAuthClient } from '@upvave/hds-auth/client';
export const auth = createAuthClient({
authServerUrl: 'https://auth.example.com',
appId: 'salma',
});Login and Logout
auth.login({ returnTo: window.location.href });
await auth.logout({
returnTo: 'https://app.example.com/login',
});To generate a URL without redirecting:
const loginUrl = auth.login({
mode: 'url',
returnTo: 'https://app.example.com/dashboard',
});Sessions
const session = await auth.getSession();
const currentUser = await auth.getCurrentUser();
await auth.refreshSession();Silent Auth and SSO
const session = await auth.silentAuthCheck();
if (!session) {
auth.login({ returnTo: window.location.href });
}const result = await auth.exchangeSsoToken({
token: 'sso_token',
returnTo: 'https://app.example.com/dashboard',
});
console.log(result.session.user.email);Invite and Password Flows
await auth.verifyInviteToken(token);
await auth.acceptInvite({ token, name, password });
await auth.setInvitePassword({ token, password });
await auth.createResetToken({ email });
await auth.verifyResetToken(token);
await auth.resetPassword({ token, password });
await auth.changePassword({ currentPassword, newPassword });Email sending stays the responsibility of the app.
React Usage
Wrap the app once with AuthProvider, then read auth state with hooks.
import { AuthProvider, useSession } from '@upvave/hds-auth/client';
import { auth } from './auth';
export function AppProviders({ children }: { children: React.ReactNode }) {
return <AuthProvider client={auth}>{children}</AuthProvider>;
}
export function AccountMenu() {
const { user, status } = useSession();
if (status === 'loading') return null;
if (!user) {
return <button onClick={() => auth.login({ returnTo: location.href })}>Login</button>;
}
return <button onClick={() => void auth.logout()}>Logout {user.email}</button>;
}You can also pass a client directly:
const state = useSession(auth);
const user = useUser(auth);
const status = useAuthStatus(auth);Server Usage
Use the server entrypoint in API routes, backend services, server actions, middleware, and other trusted environments.
import { createAuthServerClient } from '@upvave/hds-auth/server';
const auth = createAuthServerClient();
const session = await auth.requireAuth({
headers: request.headers,
});
auth.requirePermission(session, 'users:read');Server calls are signed with HDS_APP_SECRET. The raw secret is never sent as a request header.
User Administration
await auth.addUser(
{
email: '[email protected]',
role: 'stylist',
permissions: ['appointments:read'],
},
{ headers: request.headers },
);
const users = await auth.getUsers({ role: 'stylist', limit: 25 }, { headers: request.headers });
const user = await auth.getUserByEmail('[email protected]', { headers: request.headers });
await auth.updateUser('user_123', { name: 'Senior Stylist' }, { headers: request.headers });
await auth.disableUser('user_123', { headers: request.headers });
await auth.enableUser('user_123', { headers: request.headers });
await auth.removeUserFromApp('user_123', { headers: request.headers });
await auth.deleteUser('user_123', { headers: request.headers });Roles and Permissions
await auth.updateUserRole('user_123', 'manager', { headers: request.headers });
await auth.updateUserPermissions(
{
userId: 'user_123',
permissions: ['users:read', 'appointments:write'],
},
{ headers: request.headers },
);
const canReadUsers = auth.hasPermission(session, 'users:read');
const isOwner = auth.hasRole(session, 'owner');App Profiles
const profile = await auth.getAppProfile('user_123', { headers: request.headers });
await auth.updateAppProfile(
{
userId: 'user_123',
data: {
locationId: 'loc_1',
department: 'color',
},
},
{ headers: request.headers },
);
const access = await auth.getUserAppAccess('user_123', { headers: request.headers });Invites and Passwords
const invite = await auth.createInvite(
{
email: '[email protected]',
role: 'stylist',
permissions: ['appointments:read'],
},
{ headers: request.headers },
);
await auth.verifyInviteToken(invite.token, { headers: request.headers });
await auth.setInvitePassword({ token: invite.token, password }, { headers: request.headers });
const reset = await auth.createResetToken({ email: '[email protected]' }, { headers: request.headers });
await auth.resetPassword({ token: reset.token, password }, { headers: request.headers });Next.js Usage
Use @upvave/hds-auth/next in App Router middleware, route handlers, and server actions.
Middleware
import { createNextAuth } from '@upvave/hds-auth/next';
const auth = createNextAuth();
export async function middleware(request: Request) {
return auth.middleware(request);
}Route Handlers
import { createNextAuth } from '@upvave/hds-auth/next';
const auth = createNextAuth();
export const GET = auth.routeHandler(
async ({ request, client }) => {
const users = await client.getUsers({}, { headers: request.headers });
return Response.json(users);
},
{ permission: 'users:read' },
);Server Actions
'use server';
import { headers } from 'next/headers';
import { createNextAuth } from '@upvave/hds-auth/next';
const auth = createNextAuth();
const updateRoleWithAuth = auth.serverAction(
async ({ client }, userId: string, role: string) => {
await client.updateUserRole(userId, role, { headers: await headers() });
},
{ permission: 'users:write' },
);
export async function updateRole(userId: string, role: string) {
return updateRoleWithAuth({ headers: await headers() }, userId, role);
}Error Handling
SDK errors are normalized as HdsAuthError.
import { HdsAuthError, isHdsAuthError } from '@upvave/hds-auth/errors';
try {
await auth.requireAuth({ headers: request.headers });
} catch (error) {
if (isHdsAuthError(error)) {
console.log(error.code, error.status, error.message);
}
}Common error codes:
AUTH_REQUIREDINVALID_APP_SECRETINVITE_TOKEN_EXPIREDRESET_TOKEN_EXPIREDNO_APP_ACCESSPERMISSION_DENIEDSESSION_EXPIREDINVALID_TOKENUSER_NOT_FOUNDRATE_LIMITEDCONFIGURATION_ERRORNETWORK_ERRORAUTH_SERVER_ERROR
User Guidelines
For App Developers
- Install only
@upvave/hds-auth; do not configure Better Auth directly in apps. - Use
@upvave/hds-auth/clientonly in browser code. - Use
@upvave/hds-auth/serveronly in trusted server code. - Use
@upvave/hds-auth/nextfor Next.js middleware, route handlers, and server actions. - Always pass incoming
request.headersto server calls when user context matters. - Treat
HDS_APP_SECRETas a secret. Do not expose it throughNEXT_PUBLIC_*, client props, logs, or error payloads. - Use
requireAuth,requireRole, andrequirePermissionat server boundaries before sensitive actions. - Keep email sending in the app. The SDK can create or verify tokens, but it does not send email.
- Pin SDK versions in production apps and upgrade intentionally.
For Auth Server Developers
- Keep endpoint contracts stable for existing SDK versions.
- Return clean error payloads with
{ code, message, details }where possible. - Do not rely on apps sending raw app secrets. Server SDK requests are signed.
- Add audit logs for sensitive operations such as user changes, role updates, password resets, and invite acceptance.
Security Checklist
- Use HTTPS for
HDS_AUTH_SERVER_URLoutside localhost development. - Rotate
HDS_APP_SECRETif it may have been exposed. - Keep permissions app-specific and least-privilege.
- Avoid storing plain tokens longer than needed.
- Review all route handlers and server actions for explicit auth guards.
Development
bun install
bun run lint
bun run typecheck
bun run test
bun run build
bun run checkThe package is ESM-only, strict TypeScript, and targets Node.js >=20.19.0.
Publishing
Automated releases run from GitHub Actions when a stable version tag is pushed:
git tag v1.2.3
git push origin v1.2.3The workflow updates package.json to 1.2.3, commits that version back to the default branch, verifies the package, and publishes it to npm.
For private GitHub repositories, configure an npm automation token as the GitHub Actions secret NPM_TOKEN before using the automated release workflow.
Before publishing:
bun run check
npm pack --dry-run
npm publish --access publicFor a public GitHub source repository where provenance is available:
npm publish --access public --provenanceRelease checklist:
- Keep
package.jsonversion aligned with the intended release. - Keep public subpath exports aligned with the README entrypoints.
- Confirm
filesonly includesdist,README.md, andLICENSE. - Keep
HDS_APP_SECRETserver-only in examples and docs. - Run
npm pack --dry-runand inspect the tarball before publishing. - Pin app installs to an exact SDK version for production apps.
Use Changesets for version bumps:
bun run changeset
bun run versionLicense
Public source, personal-use license. This package is not private, but it is not open source under an OSI-approved license.
Personal, educational, evaluation, and non-commercial use is allowed. Commercial, production, organization, agency, client, enterprise, hosted, SaaS, resale, or redistributed use requires separate written permission from Upvave.
See LICENSE.
