@com-tech/central-auth-sdk
v1.0.0
Published
Official TypeScript SDK for Central Auth - Multi-tenant authentication and user management
Downloads
101
Maintainers
Readme
Central Auth SDK
Official TypeScript SDK for Central Auth.
Installation
npm install @com-tech/central-auth-sdkQuick Start
import { CentralAuth } from '@com-tech/central-auth-sdk';
const auth = new CentralAuth({
apiKey: process.env.CENTRAL_AUTH_API_KEY!, // app_sk_... — get from your app's API Key tab
baseUrl: process.env.CENTRAL_AUTH_BASE_URL, // optional; defaults to https://my.communitytech.co.uk
});
const register = await auth.auth.register({
email: '[email protected]',
password: 'SecureP@ss123',
name: 'Jane Doe',
});
console.log(register.user.email, register.sessionToken);
const login = await auth.auth.login({
email: '[email protected]',
password: 'SecureP@ss123',
});
if ('requires2FA' in login) {
const verified = await auth.auth.verify2FA({
challengeToken: login.challengeToken,
code: '123456',
});
console.log(verified.sessionToken);
} else {
console.log(login.sessionToken);
}60-Second Express Integration
import express from 'express';
import { CentralAuth, requireAuth, webhookHandler } from '@com-tech/central-auth-sdk';
const app = express();
const auth = new CentralAuth({
apiKey: process.env.CENTRAL_AUTH_API_KEY!,
});
// Webhooks need the raw body — mount before express.json().
app.post(
'/webhooks/central-auth',
express.raw({ type: 'application/json' }),
webhookHandler(process.env.CENTRAL_AUTH_WEBHOOK_SECRET!, {
'subscription.created': async (event) => {
console.log('subscription created', event.data.subscription.id);
},
}),
);
app.use(express.json());
app.post('/signup', async (req, res) => res.json(await auth.auth.register(req.body)));
app.post('/login', async (req, res) => res.json(await auth.auth.login(req.body)));
app.get('/me', requireAuth(auth), (req, res) => res.json({ user: req.user }));
app.listen(3000);The
app_sk_*key alone identifies your app — there is no separateappIdconfig. Keep it server-side.
Auth Methods
// Register/login
await auth.auth.register({ email, password, name, appSlug: 'my-app-slug' });
await auth.auth.login({ email, password, appSlug: 'my-app-slug' });
// Profile
const user = await auth.auth.getUser(sessionToken);
await auth.auth.updateUser(sessionToken, { name: 'Updated Name' });
// Passwords
await auth.auth.requestPasswordReset('[email protected]');
await auth.auth.resetPassword(resetToken, 'NewSecureP@ss123');
await auth.auth.changePassword(sessionToken, {
currentPassword: 'OldSecureP@ss123',
newPassword: 'NewSecureP@ss123',
});
// Magic link
await auth.auth.sendMagicLink('[email protected]');
await auth.auth.verifyMagicLink(tokenFromEmail);
// Email verification
await auth.auth.verifyEmail({ token: tokenFromEmail });
await auth.auth.resendVerification({ email: '[email protected]' });
// 2FA
await auth.auth.setup2FA(sessionToken);
await auth.auth.confirm2FA(sessionToken, { code: '123456' });
await auth.auth.disable2FA(sessionToken, { password: 'CurrentPassword' });
await auth.auth.regenerateBackupCodes(sessionToken);
// Switch active organization
await auth.auth.switchOrganization(sessionToken, { organizationSlug: 'acme-corp' });
// Validate a user-issued sk_* API key from the calling app
await auth.auth.validateUserApiKey('sk_user_xxx');
// Logout
await auth.auth.logout(sessionToken);
await auth.auth.logoutAll(sessionToken);Notes:
refreshToken(refreshToken, organizationId?)rotates a session/refresh pair. Both tokens come back fromregisterandlogin; store both server-side and rotate when the session token expires.logoutAll()revokes every active session for the token owner.
Apps Methods
// Fetch the current user's app registration, API key, plan, and custom data.
const myApp = await auth.apps.getMyApp(sessionToken, 'my-app-slug');
// Rotate the current user's sk_user_* key for that app.
const apiKey = await auth.apps.regenerateKey(sessionToken, myApp.app.id);
// Read and update per-app custom settings stored on app_user.customData.
const settings = await auth.apps.getSettings(sessionToken, 'my-app-slug');
await auth.apps.updateSettings(sessionToken, 'my-app-slug', {
...settings,
theme: 'dark',
});Billing Methods
// App plans (pass app slug)
const { plans } = await auth.billing.listPlans('my-app-slug');
// Current subscription (optionally scoped to appId)
const { subscription } = await auth.billing.getSubscription(sessionToken, 12);
// Create checkout
const checkout = await auth.billing.createCheckoutSession(sessionToken, {
planId: 3,
interval: 'monthly',
successUrl: 'https://app.example.com/billing/success',
cancelUrl: 'https://app.example.com/billing/cancel',
});
// Portal
const portal = await auth.billing.getCustomerPortal(sessionToken, {
returnUrl: 'https://app.example.com/settings/billing',
});
// Cancellation flow (initiates survey/offers)
// `reason` is a required literal-union; see CancellationReason in types.ts.
const cancellation = await auth.billing.cancelSubscription(sessionToken, {
subscriptionId: 42,
reason: 'too_expensive',
});
// To change plans, cancel and start a fresh checkout — there's no in-place update.
// To change a plan, cancel and re-checkout.
// Invoices
const invoices = await auth.billing.getInvoices(sessionToken, {
status: 'paid',
page: 1,
perPage: 20,
});Organization Methods
Organization methods accept an organization slug or numeric id. If you pass an id, the SDK resolves it to a slug first.
const created = await auth.organizations.create(sessionToken, {
name: 'Acme Corp',
slug: 'acme-corp',
});
const { organizations } = await auth.organizations.list(sessionToken);
const organization = await auth.organizations.get(sessionToken, created.slug);
await auth.organizations.update(sessionToken, created.slug, {
name: 'Acme Corporation',
});
await auth.organizations.inviteMember(sessionToken, created.slug, {
email: '[email protected]',
role: 'member',
});
const members = await auth.organizations.listMembers(sessionToken, created.slug);
await auth.organizations.updateMember(sessionToken, created.slug, 123, {
role: 'admin',
});
await auth.organizations.removeMember(sessionToken, created.slug, 123);
await auth.organizations.delete(sessionToken, created.slug, {
password: 'CurrentPassword',
confirmDeletion: true,
});Admin Methods
const users = await auth.listUsers(adminToken, { page: 1, perPage: 25 });
const oneUser = await auth.getUser(adminToken, 123);
await auth.updateUser(adminToken, 123, { name: 'Updated' });
await auth.suspendUser(adminToken, 123, 'Policy violation');
await auth.activateUser(adminToken, 123);
await auth.deleteUser(adminToken, 123);Webhooks
webhookHandler(secret, handlers, options?) returns an Express/Fastify-compatible request handler. Mount with express.raw({ type: 'application/json' }) before express.json() so the signed bytes match exactly.
import express from 'express';
import { webhookHandler } from '@com-tech/central-auth-sdk';
const app = express();
app.post(
'/webhooks/central-auth',
express.raw({ type: 'application/json' }),
webhookHandler(process.env.CENTRAL_AUTH_WEBHOOK_SECRET!, {
'user.registered': async (event) => {
console.log(event.data.user.email);
},
'subscription.created': async (event) => {
console.log(event.data.subscription.id);
},
'payment.completed': async (event) => {
console.log(event.data.amount);
},
}),
);Signatures are Stripe-style (X-Webhook-Signature: t=<unix-seconds>,v1=<hex-hmac-sha256>) and the SDK rejects deliveries whose timestamp is more than 5 minutes off the verifying server's clock. For non-Express runtimes use CentralAuth.verifyWebhookSignature(...) / CentralAuth.parseWebhookEvent(...) directly.
Express Middleware
import express from 'express';
import { CentralAuth, requireAuth } from '@com-tech/central-auth-sdk';
const app = express();
const auth = new CentralAuth({ apiKey: process.env.CENTRAL_AUTH_API_KEY! });
app.get('/api/profile', requireAuth(auth), (req, res) => {
res.json({ user: req.user });
});requireAuth(auth, options?) extracts a Bearer token from the Authorization header (override via options.getToken), validates it with auth.auth.getUser, and sets req.user. Pass { required: false } to make auth optional and let unauthenticated requests through with req.user = null.
Error Handling
import { AuthenticationError, ValidationError } from '@com-tech/central-auth-sdk';
try {
await auth.auth.login({ email, password });
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Invalid credentials');
} else if (error instanceof ValidationError) {
console.error(error.message);
}
}Contributing — Contract Tests Are Mandatory
If you change the SDK's public surface — adding/removing a method, renaming
arguments, changing a thrown error class, altering request bodies, etc. — you
MUST also update the shared SDK contract suite at
../sdk-contracts/. It is the source of truth for SDK behavior across all
languages (TS today; PHP / Python / React Native / Flutter in the future).
After your change, run:
# 1. SDK self-tests
cd sdk && npm run typecheck && npm test && npm run build
# 2. Contract runner self-tests
cd ../sdk-contracts/runner && npm test
# 3. TypeScript SDK adapter — runs every contract against this SDK
cd ../adapters/typescript-sdk && npm testIf a contract fixture needs to change because of your edit, see
sdk-contracts/README.md for the contributor flow and the matcher syntax
used in fixtures/*.json. If the change reveals a doc-vs-code drift, log it
in sdk-contracts/openapi-notes.md.
