Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
30c7bc1
fix: change refresh call to a post, not get
Bccorb Apr 22, 2026
b942dad
chore: version bump
Bccorb Apr 22, 2026
774f065
test: fix a test for refresh endpoint
Bccorb Apr 22, 2026
cf6002d
chore: version package and seamless core
Bccorb Apr 23, 2026
f9f9791
fix: referesh token should pass the refresh token
Bccorb Apr 23, 2026
3d52e7a
chore: version bump
Bccorb Apr 23, 2026
1fb41ce
chore: core upgrade and version bump
Bccorb Apr 23, 2026
8f85d26
fix: refresh token race condition
Bccorb Apr 23, 2026
6801422
chore: version bump
Bccorb Apr 23, 2026
3624e21
chore: core upgrade and version bump
Bccorb Apr 23, 2026
0a2c41f
fix: added referesh token cache to stop race conditions
Bccorb Apr 23, 2026
ec1e359
chore: version bump
Bccorb Apr 23, 2026
94ebd97
chore: version bump
Bccorb Apr 23, 2026
72704de
fix(core): add cookie paths to magic links
Bccorb Apr 25, 2026
2fc8596
chore: bump package version for core
Bccorb Apr 26, 2026
e387ddc
chore: version bump
Bccorb Apr 26, 2026
50fcc25
feat: forward ips through to auth server
Bccorb May 3, 2026
cd73aee
chore: version bump for core
Bccorb May 5, 2026
b430fc6
chore: version bump core package in express
Bccorb May 5, 2026
167d6ae
chore: version bump express version
Bccorb May 5, 2026
1b092f5
feat: implement step up authentication
Bccorb May 16, 2026
99a6cb7
feat supprt PRF webauthn flows
Bccorb May 16, 2026
7184b7f
Merge pull request #18 from fells-code/step-up-authentication
Bccorb May 17, 2026
2eed553
Merge pull request #19 from fells-code/prf-implementation
Bccorb May 17, 2026
ceb0856
feat: otp login and login methods
Bccorb May 17, 2026
1a02b9c
chore: bump and lock core package
Bccorb May 17, 2026
f9ded46
Merge pull request #20 from fells-code/otp-logins
Bccorb May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ It is also the natural initializer boundary for adopter-supplied auth messaging
- custom auth-message handlers
- optional auth template overrides

For WebAuthn PRF flows, the adapter proxies PRF registration query flags and assertion request bodies to the Seamless Auth API. PRF outputs remain browser-only and are never handled by the server adapter.

Location:

```
Expand Down
4 changes: 2 additions & 2 deletions packages/core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@seamless-auth/core",
"version": "0.4.0",
"version": "0.5.0",
"description": "Framework-agnostic core authentication logic for SeamlessAuth",
"license": "AGPL-3.0-only",
"author": "Fells Code, LLC",
Expand Down Expand Up @@ -53,4 +53,4 @@
"publishConfig": {
"access": "public"
}
}
}
11 changes: 11 additions & 0 deletions packages/core/src/authFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface AuthFetchOptions {
headers?: Record<string, string>;
body?: unknown;
authorization?: string;
serviceAuthorization?: string;
forwardedClientIp?: string;
}

export async function authFetch(
Expand All @@ -17,6 +19,15 @@ export async function authFetch(
"Content-Type": "application/json",
...options.headers,
...(options.authorization ? { Authorization: options.authorization } : {}),
...((options.serviceAuthorization ?? options.authorization)
? {
"x-seamless-service-token":
options.serviceAuthorization ?? options.authorization!,
}
: {}),
...(options.forwardedClientIp
? { "x-seamless-client-ip": options.forwardedClientIp }
: {}),
};

return fetch(url, {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/createServiceToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface ServiceTokenOptions {
issuer: string;
audience: string;
subject: string;
sessionId?: string;
refreshToken?: string;
serviceSecret: string;
keyId: string;
Expand All @@ -15,6 +16,7 @@ export function createServiceToken(opts: ServiceTokenOptions): string {
iss: opts.issuer,
aud: opts.audience,
sub: opts.subject,
...(opts.sessionId === undefined ? {} : { sid: opts.sessionId }),
refreshToken: opts.refreshToken,
iat: Math.floor(Date.now() / 1000),
},
Expand Down
44 changes: 43 additions & 1 deletion packages/core/src/ensureCookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ export interface EnsureCookiesInput {

export interface CookiePayload {
sub: string;
sessionId?: string;
token?: string;
refreshToken?: string;
roles?: string[];
email?: string;
phone?: string;
phone?: string | null;
}

export interface CookieInstruction {
Expand All @@ -28,6 +29,7 @@ export interface EnsureCookiesResult {
error?: string;
user?: {
sub: string;
sessionId?: string;
roles?: string[];
};
setCookies?: CookieInstruction[];
Expand All @@ -46,6 +48,7 @@ export interface EnsureCookiesOptions {
issuer: string;
audience: string;
keyId: string;
forwardedClientIp?: string;
}

const COOKIE_REQUIREMENTS: Record<
Expand Down Expand Up @@ -78,8 +81,35 @@ const COOKIE_REQUIREMENTS: Record<
name: "registrationCookieName",
required: true,
},
"/otp/verify-login-email-otp": {
name: "preAuthCookieName",
required: true,
},
"/otp/verify-login-phone-otp": {
name: "preAuthCookieName",
required: true,
},
"/otp/generate-login-email-otp": {
name: "preAuthCookieName",
required: true,
},
"/otp/generate-login-phone-otp": {
name: "preAuthCookieName",
required: true,
},
"/magic-link": {
name: "preAuthCookieName",
required: true,
},
"/magic-link/check": {
name: "preAuthCookieName",
required: true,
},
"/logout": { name: "accessCookieName", required: true },
"/users/me": { name: "accessCookieName", required: true },
"/step-up/status": { name: "accessCookieName", required: true },
"/step-up/webauthn/start": { name: "accessCookieName", required: true },
"/step-up/webauthn/finish": { name: "accessCookieName", required: true },
"/internal/metrics/dashboard": { name: "accessCookieName", required: true },
"/internal/auth-events/timeseries": {
name: "accessCookieName",
Expand Down Expand Up @@ -163,6 +193,7 @@ export async function ensureCookies(
issuer: opts.issuer,
audience: opts.audience,
keyId: opts.keyId,
forwardedClientIp: opts.forwardedClientIp,
});

if (!refreshed?.token) {
Expand All @@ -182,14 +213,22 @@ export async function ensureCookies(
type: "ok",
user: {
sub: refreshed.sub,
...(refreshed.sessionId === undefined
? {}
: { sessionId: refreshed.sessionId }),
roles: refreshed.roles,
},
setCookies: [
{
name: cookieName,
value: {
sub: refreshed.sub,
...(refreshed.sessionId === undefined
? {}
: { sessionId: refreshed.sessionId }),
roles: refreshed.roles,
email: refreshed.email,
phone: refreshed.phone,
},
ttl: refreshed.ttl,
domain: opts.cookieDomain,
Expand Down Expand Up @@ -221,6 +260,9 @@ export async function ensureCookies(
type: "ok",
user: {
sub: payload.sub as string,
...(typeof payload.sessionId === "string"
? { sessionId: payload.sessionId }
: {}),
roles: payload.roles as string[] | undefined,
},
};
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/getSeamlessUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface GetSeamlessUserOptions {
cookieSecret: string;
authorization: string;
cookieName?: string;
forwardedClientIp?: string;
}

/**
Expand All @@ -32,9 +33,8 @@ export async function getSeamlessUser<T = any>(

const response = await authFetch(`${opts.authServerUrl}/users/me`, {
method: "GET",
headers: {
Authorization: opts.authorization,
},
authorization: opts.authorization,
forwardedClientIp: opts.forwardedClientIp,
});

if (!response.ok) return null;
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/handlers/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { authFetch } from "../authFetch.js";
type BaseOpts = {
authServerUrl: string;
authorization?: string;
forwardedClientIp?: string;
};

type WithQuery = BaseOpts & {
Expand Down Expand Up @@ -42,6 +43,7 @@ async function request(
method,
authorization: opts.authorization,
body: opts.body,
forwardedClientIp: opts.forwardedClientIp,
},
);

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/handlers/bootstrapAdminInvite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface BootstrapAdminInviteOptions {
email: string;
authorization?: string;
externalDelivery?: boolean;
forwardedClientIp?: string;
}

export interface BootstrapAdminInviteResult {
Expand All @@ -24,8 +25,9 @@ export async function bootstrapAdminInviteHandler(
`${opts.authServerUrl}/internal/bootstrap/admin-invite`,
{
method: "POST",
authorization: opts.authorization,
forwardedClientIp: opts.forwardedClientIp,
headers: {
authorization: opts.authorization || "",
...(opts.externalDelivery
? {
"x-seamless-auth-delivery-mode": "external",
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/handlers/finishLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { verifySignedAuthResponse } from "../verifySignedAuthResponse.js";
export interface FinishLoginInput {
body: unknown;
authorization?: string;
forwardedClientIp?: string;
}

export interface FinishLoginOptions {
Expand Down Expand Up @@ -34,6 +35,7 @@ export async function finishLoginHandler(
method: "POST",
body: input.body,
authorization: input.authorization,
forwardedClientIp: input.forwardedClientIp,
});

const data = await up.json();
Expand All @@ -58,6 +60,11 @@ export async function finishLoginHandler(
throw new Error("Signature mismatch with data payload");
}

const sessionId =
typeof verifiedAccessToken.sid === "string"
? verifiedAccessToken.sid
: undefined;

return {
status: 200,
body: data,
Expand All @@ -66,6 +73,7 @@ export async function finishLoginHandler(
name: opts.accessCookieName,
value: {
sub: data.sub,
...(sessionId === undefined ? {} : { sessionId }),
roles: data.roles,
email: data.email,
phone: data.phone,
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/handlers/finishRegister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface FinishRegisterInput {
authorization?: string;
headers?: Record<string, string>;
body: unknown;
forwardedClientIp?: string;
}

export interface FinishRegisterOptions {
Expand Down Expand Up @@ -35,6 +36,7 @@ export async function finishRegisterHandler(
authorization: input.authorization,
headers: input.headers,
body: input.body,
forwardedClientIp: input.forwardedClientIp,
});

const data = await up.json();
Expand All @@ -59,13 +61,17 @@ export async function finishRegisterHandler(
throw new Error("Signature mismatch with data payload");
}

const sessionId =
typeof verified.sid === "string" ? verified.sid : undefined;

return {
status: 204,
setCookies: [
{
name: opts.accessCookieName,
value: {
sub: data.sub,
...(sessionId === undefined ? {} : { sessionId }),
roles: data.roles,
email: data.email,
phone: data.phone,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/handlers/internalMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { authFetch } from "../authFetch.js";
type BaseOpts = {
authServerUrl: string;
authorization?: string;
forwardedClientIp?: string;
};

type WithQuery = BaseOpts & {
Expand Down Expand Up @@ -32,6 +33,7 @@ async function get(path: string, opts: WithQuery): Promise<Result> {
{
method: "GET",
authorization: opts.authorization,
forwardedClientIp: opts.forwardedClientIp,
},
);

Expand Down
26 changes: 24 additions & 2 deletions packages/core/src/handlers/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ export interface LoginOptions {
authServerUrl: string;
cookieDomain?: string;
preAuthCookieName: string;
forwardedClientIp?: string;
}

export interface LoginResult {
status: number;
error?: string;
body?: {
message?: string;
identifierType?: string;
loginMethods?: string[];
};
error?: unknown;
setCookies?: {
name: string;
value: CookiePayload;
Expand All @@ -30,6 +36,7 @@ export async function loginHandler(
const up = await authFetch(`${opts.authServerUrl}/login`, {
method: "POST",
body: input.body,
forwardedClientIp: opts.forwardedClientIp,
});

const data = await up.json();
Expand All @@ -54,8 +61,23 @@ export async function loginHandler(
throw new Error("Signature mismatch with data payload");
}

const body = {
...(typeof data.message === "string" ? { message: data.message } : {}),
...(typeof data.identifierType === "string"
? { identifierType: data.identifierType }
: {}),
...(Array.isArray(data.loginMethods)
? {
loginMethods: data.loginMethods.filter(
(item: unknown) => typeof item === "string",
),
}
: {}),
};

return {
status: 204,
status: up.status,
body,
setCookies: [
{
name: opts.preAuthCookieName,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/handlers/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface LogoutOptions {
accessCookieName: string;
registrationCookieName: string;
refreshCookieName: string;
forwardedClientIp?: string;
}

export interface LogoutResult {
Expand All @@ -17,6 +18,7 @@ export async function logoutHandler(
): Promise<LogoutResult> {
await authFetch(`${opts.authServerUrl}/logout`, {
method: "GET",
forwardedClientIp: opts.forwardedClientIp,
});

return {
Expand Down
Loading
Loading