Skip to content

@eventista/sdk-auth

Authentication SDK for Eventista. Provides the AuthClient class with login, register, refresh, logout flows. Cross-platform (web / Node / React Native) — token persistence is delegated to a consumer-supplied TokenStorageAdapter.

The class shape mirrors RestClient from @eventista/sdk-api-client for consistency across the SDK suite.

Terminal window
bun add @eventista/sdk-auth @eventista/sdk-api-client @eventista/sdk-core
import { AuthClient, AuthEnvironment } from "@eventista/sdk-auth";
const auth = new AuthClient({
environment: AuthEnvironment.Production, // or AuthEnvironment.Development
onTokensChanged: (tokens) => {
if (tokens) {
console.log("signed in until", new Date(tokens.expiresAt));
} else {
console.log("signed out");
}
},
});
const result = await auth.login({ email: "a@b.test", password: "secret" });
if (result.isErr) {
if (result.code === "AUTH_INVALID_CREDENTIALS") {
console.error("wrong password");
} else {
console.error(result.code, result.errorCode, result.err);
}
} else {
console.log("welcome", result.data.user.email);
}

The SDK manages the underlying Account service URL per environment — consumers do not need to know infra-level URLs. Pass AuthEnvironment.Production for live traffic, AuthEnvironment.Development for the dev/staging tier.

Every AuthClient method returns an AuthResponse<T> discriminated result. Methods never throw on auth failure — branch on result.isErr first, then on result.code for typed error handling.

FieldWhenDescription
isErralwaysfalse on success, true on failure.
datasuccess onlyMethod-specific payload (e.g. LoginResult, RegisterResult).
codeerror onlyStable {@link AuthErrorCode} from {@link AUTH_ERROR_CODES}. Branch on this — it is part of the public API.
errorCodeerror only (when available)Raw upstream code (number or string, e.g. 101009). Surface this in support tickets without parsing.
errerror onlyHuman-readable message.
const result = await auth.register({ email, password, redirectUri });
if (result.isErr) {
// result.code is AuthErrorCode; result.errorCode is the raw BE code.
return showError(result.code, result.errorCode, result.err);
}
// result.data is RegisterResult.
if (result.data.status === "verified") {
setUser(result.data.user);
} else {
// status === "pendingVerification"
showVerifyEmailNotice();
}

Email verification itself is fully server-side: the backend mails a callback URL (built from redirectUri) with an embedded token; clicking the link triggers verification on the BE and redirects back with status in the URL params. The SDK never makes a verify-email HTTP call — consumer apps only need to parse the URL params on their callback page.

The default MemoryTokenStorageAdapter stores tokens in memory only — fine for tests and SSR, but not persistent across page reloads. Wire your own per platform.

Use localStorage so tokens survive a hard refresh. Only construct the client on the client side — localStorage is not available during SSR.

lib/auth.ts
"use client";
import {
AuthClient,
AuthEnvironment,
type TokenStorageAdapter,
} from "@eventista/sdk-auth";
const KEY = "eventista_auth_tokens";
const webStorage: TokenStorageAdapter = {
async getTokens() {
const raw = window.localStorage.getItem(KEY);
return raw ? JSON.parse(raw) : null;
},
async setTokens(tokens) {
window.localStorage.setItem(KEY, JSON.stringify(tokens));
},
async clearTokens() {
window.localStorage.removeItem(KEY);
},
};
export const auth = new AuthClient({
environment: AuthEnvironment.Production,
storage: webStorage,
});
app/login/page.tsx
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { auth } from "@/lib/auth";
export default function LoginPage() {
const router = useRouter();
const [error, setError] = useState<string | null>(null);
async function onSubmit(formData: FormData) {
const result = await auth.login({
email: String(formData.get("email")),
password: String(formData.get("password")),
});
if (result.isErr) {
setError(
result.code === "AUTH_INVALID_CREDENTIALS"
? "Wrong email or password"
: result.err,
);
return;
}
router.push("/");
}
return (
<form action={onSubmit}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Sign in</button>
{error && <p>{error}</p>}
</form>
);
}

Use @react-native-async-storage/async-storage for persistence across app restarts.

lib/auth.ts
import {
AuthClient,
AuthEnvironment,
type TokenStorageAdapter,
} from "@eventista/sdk-auth";
import AsyncStorage from "@react-native-async-storage/async-storage";
const KEY = "eventista_auth_tokens";
const rnStorage: TokenStorageAdapter = {
async getTokens() {
const raw = await AsyncStorage.getItem(KEY);
return raw ? JSON.parse(raw) : null;
},
async setTokens(tokens) {
await AsyncStorage.setItem(KEY, JSON.stringify(tokens));
},
async clearTokens() {
await AsyncStorage.removeItem(KEY);
},
};
export const auth = new AuthClient({
environment: AuthEnvironment.Production,
storage: rnStorage,
});
app/login.tsx
import { useState } from "react";
import { Button, Text, TextInput, View } from "react-native";
import { router } from "expo-router";
import { auth } from "../lib/auth";
export default function LoginScreen() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
async function handleLogin() {
const result = await auth.login({ email, password });
if (result.isErr) {
setError(
result.code === "AUTH_INVALID_CREDENTIALS"
? "Wrong email or password"
: result.err,
);
return;
}
router.replace("/");
}
return (
<View>
<TextInput
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
/>
<TextInput value={password} onChangeText={setPassword} secureTextEntry />
<Button title="Sign in" onPress={handleLogin} />
{error && <Text>{error}</Text>}
</View>
);
}
MethodDescription
loginEmail/password sign-in
loginWithGoogleGoogle OAuth sign-in (id_token)
registerCreate a new account
forgotPasswordTrigger a password-reset email
resetPasswordSet a new password using a reset token
changePasswordChange password for the signed-in user
refreshExchange a refresh token for a new access token
logoutRevoke refresh token and clear local state
getUserInfoFetch the authenticated user’s profile
getTokensRead currently stored tokens
clearTokensDrop local tokens without calling the backend

All methods return Promise<AuthResponse<T>> — see the response shape section above. getTokens and clearTokens are local-only and return plain values (AuthTokens | null and void).

WIP. Public API is unstable — do not depend on it for production yet.

Internally consolidated: every method’s logic lives directly on AuthClient. There is no separate services/ module — to extend the SDK, add a method on the class.

Migration from earlier (throwing) versions

Section titled “Migration from earlier (throwing) versions”

Prior versions threw an AuthError class on failure. That class has been removed. Replace try/catch with if (result.isErr):

// Before
try {
const result = await auth.login({ email, password });
console.log(result.user);
} catch (err) {
if (err instanceof AuthError && err.code === "AUTH_INVALID_CREDENTIALS") {
// ...
}
}
// After
const result = await auth.login({ email, password });
if (result.isErr) {
if (result.code === "AUTH_INVALID_CREDENTIALS") {
// ...
}
} else {
console.log(result.data.user);
}

Field renames on the failure path:

  • err.coderesult.code (unchanged semantic — AuthErrorCode)
  • err.backendErrorCoderesult.errorCode (raw upstream code)
  • err.messageresult.err (human message; matches APIResponse from @eventista/sdk-api-client)