Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
"examples/gate-private2-static/public/assets/*",
"examples/nextjs/tests/*.mjs",
"examples/nuxt/tests/*.mjs",
"gate/dev/bootstrap-manager-stack.mjs",
"gate/dev/render-magic-gate-config.mjs",
"manager/dev/bootstrap-local-managed-stack.mjs",
"manager/dev/bootstrap-managed-stack.mjs",
"packages/core/test/fixtures/*.mjs",
"packages/nuxt/src/runtime/app/**/*.ts",
"packages/nuxt/src/runtime/server/routes/*.ts",
"tests/e2e/src/run-web-server.mjs"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions docs/assets/logos/MagicLinkSSO-social-preview.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 11 additions & 10 deletions examples/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@
"typecheck": "tsc -p tsconfig.typecheck.json"
},
"dependencies": {
"@angular/common": "22.1.3",
"@angular/core": "22.1.3",
"@angular/forms": "22.1.3",
"@angular/platform-browser": "22.1.3",
"@angular/platform-server": "22.1.3",
"@angular/router": "22.1.3",
"@angular/ssr": "22.1.5",
"@angular/common": "22.1.6",
"@angular/core": "22.1.6",
"@angular/forms": "22.1.6",
"@angular/platform-browser": "22.1.6",
"@angular/platform-server": "22.1.6",
"@angular/router": "22.1.6",
"@angular/ssr": "22.1.8",
"@magic-link-sso/angular": "workspace:*",
"@magic-link-sso/config-core": "workspace:*",
"@magic-link-sso/core": "workspace:*",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"magic-sso-example-ui": "workspace:*",
Expand All @@ -30,9 +31,9 @@
"zone.js": "^0.16.2"
},
"devDependencies": {
"@angular/build": "^22.1.5",
"@angular/cli": "22.1.5",
"@angular/compiler-cli": "22.1.3",
"@angular/build": "^22.1.8",
"@angular/cli": "22.1.8",
"@angular/compiler-cli": "22.1.6",
"@types/express": "^5.0.6",
"@types/node": "^25.9.5",
"typescript": "~6.0.3",
Expand Down
29 changes: 13 additions & 16 deletions examples/angular/src/app/login-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,20 @@ export function getAppOrigin(request: Request | null | undefined): string {
return 'http://localhost:3004';
}

const loginErrorMessages: Record<string, string> = {
'invalid-session': 'Your session could not be verified. Please sign in again.',
'missing-verification-token': 'The sign-in link is incomplete. Please request a new email.',
'session-verification-failed':
'The app could not verify the returned sign-in token. Check that MAGICSSO_JWT_SECRET matches the SSO server.',
'session-verification-misconfigured':
'This app is missing MAGICSSO_JWT_SECRET, so it cannot verify sign-in tokens.',
'verify-email-failed':
'We could not complete sign-in from that email link. Please request a new one.',
'verify-email-misconfigured': 'This app is missing required SSO verify-email configuration.',
};

export function getLoginErrorMessage(errorCode: string | undefined): string | undefined {
switch (errorCode) {
case 'invalid-session':
return 'Your session could not be verified. Please sign in again.';
case 'missing-verification-token':
return 'The sign-in link is incomplete. Please request a new email.';
case 'session-verification-failed':
return 'The app could not verify the returned sign-in token. Check that MAGICSSO_JWT_SECRET matches the SSO server.';
case 'session-verification-misconfigured':
return 'This app is missing MAGICSSO_JWT_SECRET, so it cannot verify sign-in tokens.';
case 'verify-email-failed':
return 'We could not complete sign-in from that email link. Please request a new one.';
case 'verify-email-misconfigured':
return 'This app is missing required SSO verify-email configuration.';
default:
return undefined;
}
return typeof errorCode === 'string' ? loginErrorMessages[errorCode] : undefined;
}

export function normaliseReturnUrl(returnUrl: string | undefined, appOrigin: string): string {
Expand Down
32 changes: 3 additions & 29 deletions examples/angular/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import 'dotenv/config';
import { escapeHtml, readCookieValue, safeCompare } from '@magic-link-sso/config-core/runtime';
import { isVerifyEmailPreviewResponse, isVerifyEmailResponse } from '@magic-link-sso/core';
import express, {
type NextFunction as ExpressNextFunction,
type Request as ExpressRequest,
Expand All @@ -27,7 +28,8 @@ import {
verifyAuthToken,
verifyRequestAuth,
} from '@magic-link-sso/angular';
import { buildFailureResult, readMessage, readServerUrlConfigError } from './signin-utils';
import { buildFailureResult, readMessage } from 'magic-sso-example-ui/signin';
import { readServerUrlConfigError } from './signin-utils';
export { AngularAppEngine } from '@angular/ssr';

interface SignInRequestBody {
Expand All @@ -50,14 +52,6 @@ interface VerifyOtpRequestBody {
returnUrl?: string;
}

interface VerifyEmailResponse {
accessToken: string;
}

interface VerifyEmailPreviewResponse {
email: string;
}

type AsyncExpressHandler = (
request: ExpressRequest,
response: ExpressResponse,
Expand Down Expand Up @@ -158,26 +152,6 @@ function readVerifyOtpRequestBody(value: unknown): VerifyOtpRequestBody {
};
}

function isVerifyEmailResponse(value: unknown): value is VerifyEmailResponse {
return (
typeof value === 'object' &&
value !== null &&
'accessToken' in value &&
typeof value.accessToken === 'string' &&
value.accessToken.length > 0
);
}

function isVerifyEmailPreviewResponse(value: unknown): value is VerifyEmailPreviewResponse {
return (
typeof value === 'object' &&
value !== null &&
'email' in value &&
typeof value.email === 'string' &&
value.email.length > 0
);
}

function buildLoginRedirectUrl(request: ExpressRequest, returnUrl: string, error?: string): string {
const loginUrl = new URL('/login', getRequestOrigin(request));
loginUrl.searchParams.set('returnUrl', returnUrl);
Expand Down
37 changes: 2 additions & 35 deletions examples/angular/src/signin-utils.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,8 @@
// SPDX-License-Identifier: MIT
// Copyright (C) 2026 Wojciech Polak

interface MessagePayload {
message: string;
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}

export function readMessage(value: unknown): string | null {
if (!isRecord(value)) {
return null;
}

const message = value['message'];
return typeof message === 'string' && message.length > 0 ? message : null;
}

export function buildFailureResult(
payload: unknown,
fallback = 'Failed to send verification email.',
): MessagePayload {
return {
message: readMessage(payload) ?? fallback,
};
}
import { readServerUrlConfigError as readSharedServerUrlConfigError } from 'magic-sso-example-ui/signin';

export function readServerUrlConfigError(serverUrl: string, requestOrigin: string): string | null {
try {
const parsedServerUrl = new URL(serverUrl);
if (parsedServerUrl.origin === requestOrigin) {
return 'MAGICSSO_SERVER_URL points to this Angular app. Set it to the Magic Link SSO server, usually http://localhost:3000 for local development.';
}
} catch {
return 'MAGICSSO_SERVER_URL must be an absolute URL.';
}

return null;
return readSharedServerUrlConfigError(serverUrl, requestOrigin, 'Angular');
}
3 changes: 2 additions & 1 deletion examples/angular/tests/signin-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Copyright (C) 2026 Wojciech Polak

import { describe, expect, it } from 'vitest';
import { buildFailureResult, readMessage, readServerUrlConfigError } from '../src/signin-utils';
import { buildFailureResult, readMessage } from 'magic-sso-example-ui/signin';
import { readServerUrlConfigError } from '../src/signin-utils';

describe('Angular signin utilities', () => {
it('reads a non-empty message field from payloads', () => {
Expand Down
2 changes: 1 addition & 1 deletion examples/fastify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@magic-link-sso/config-core": "workspace:*",
"@magic-link-sso/core": "workspace:*",
"dotenv": "^17.4.2",
"fastify": "^5.11.3",
"fastify": "^5.12.4",
"magic-sso-example-ui": "workspace:*"
},
"devDependencies": {
Expand Down
32 changes: 3 additions & 29 deletions examples/fastify/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import cookie from '@fastify/cookie';
import formbody from '@fastify/formbody';
import { readCookieValue, safeCompare } from '@magic-link-sso/config-core/runtime';
import { isVerifyEmailPreviewResponse, isVerifyEmailResponse } from '@magic-link-sso/core';
import Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest } from 'fastify';
import { createHmac, randomBytes } from 'node:crypto';
import { readFile } from 'node:fs/promises';
Expand All @@ -26,7 +27,8 @@ import {
renderProtectedPage,
renderVerifyEmailConfirmationPage,
} from './html.js';
import { buildFailureResult, readMessage, readServerUrlConfigError } from './signin-utils.js';
import { buildFailureResult, readMessage } from 'magic-sso-example-ui/signin';
import { readServerUrlConfigError } from './signin-utils.js';

interface SignInBody {
email?: string;
Expand Down Expand Up @@ -58,14 +60,6 @@ interface VerifyOtpBody {
returnUrl?: string;
}

interface VerifyEmailResponse {
accessToken: string;
}

interface VerifyEmailPreviewResponse {
email: string;
}

interface SignInSuccessResponse {
message: string;
otpChallengeId?: string;
Expand Down Expand Up @@ -95,26 +89,6 @@ function readString(value: unknown): string | undefined {
return typeof value === 'string' && value.length > 0 ? value : undefined;
}

function isVerifyEmailResponse(value: unknown): value is VerifyEmailResponse {
return (
typeof value === 'object' &&
value !== null &&
'accessToken' in value &&
typeof value.accessToken === 'string' &&
value.accessToken.length > 0
);
}

function isVerifyEmailPreviewResponse(value: unknown): value is VerifyEmailPreviewResponse {
return (
typeof value === 'object' &&
value !== null &&
'email' in value &&
typeof value.email === 'string' &&
value.email.length > 0
);
}

function isSignInSuccessResponse(value: unknown): value is SignInSuccessResponse {
return (
typeof value === 'object' &&
Expand Down
Loading
Loading