feat(auth): implement cross-subdomain SSO for all web apps

Add Single Sign-On (SSO) support across all mana.how subdomains:

- Add trySSO() method to @manacore/shared-auth that exchanges session
  cookies for JWT tokens
- Add /api/v1/auth/session-to-token endpoint to mana-core-auth service
- Update all 15 web apps to try SSO during auth initialization

SSO Flow:
1. User logs in on any app (e.g., calendar.mana.how)
2. Session cookie is set with Domain=.mana.how
3. When visiting another app (e.g., todo.mana.how), it checks for
   local tokens first
4. If no local tokens, tries SSO via session cookie
5. Session cookie is exchanged for JWT tokens via new endpoint
6. User is automatically authenticated

Apps updated: calendar, chat, clock, contacts, manacore, manadeck,
nutriphi, picture, planta, presi, questions, skilltree, storage,
todo, zitare

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-02 13:17:04 +01:00
parent 352070fb2f
commit feaf27dd14
19 changed files with 491 additions and 16 deletions

View file

@ -76,6 +76,7 @@ export const authStore = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -89,7 +90,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -76,6 +76,7 @@ export const authStore = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -89,7 +90,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -75,6 +75,7 @@ export const authStore = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -88,7 +89,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -76,6 +76,7 @@ export const authStore = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -89,7 +90,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -63,6 +63,7 @@ export const authStore = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -76,7 +77,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -32,11 +32,24 @@ export const authStore = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
loading = true;
try {
const isAuth = await authService.isAuthenticated();
// First, check if we have valid local tokens
let isAuth = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!isAuth) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
isAuth = true;
}
}
if (isAuth) {
const userData = await authService.getUserFromToken();
user = toManaUser(userData);

View file

@ -63,6 +63,7 @@ export const authStore = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -76,7 +77,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -77,8 +77,23 @@ export const authStore = {
try {
const authService = await getAuthService();
if (authService) {
const userData = await authService.getUserFromToken();
user = userData;
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
}
} catch (error) {
console.error('Auth initialization error:', error);

View file

@ -69,6 +69,10 @@ export const authStore = {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -81,7 +85,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -47,6 +47,7 @@ export const auth = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async init() {
if (initialized) return;
@ -60,7 +61,19 @@ export const auth = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -65,6 +65,10 @@ export const authStore = {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -77,7 +81,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -70,6 +70,10 @@ export const authStore = {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -82,7 +86,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -39,6 +39,10 @@ export const authStore = {
return initialized;
},
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -51,7 +55,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -83,6 +83,7 @@ export const authStore = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -96,7 +97,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;

View file

@ -76,6 +76,7 @@ export const authStore = {
/**
* Initialize auth state from stored tokens
* Also tries SSO if no local tokens exist (cross-domain authentication)
*/
async initialize() {
if (initialized) return;
@ -89,7 +90,19 @@ export const authStore = {
loading = true;
try {
const authenticated = await authService.isAuthenticated();
// First, check if we have valid local tokens
let authenticated = await authService.isAuthenticated();
// If not authenticated locally, try SSO (shared session cookie)
if (!authenticated) {
console.log('No local tokens, trying SSO...');
const ssoResult = await authService.trySSO();
if (ssoResult.success) {
console.log('SSO successful, user authenticated via shared session');
authenticated = true;
}
}
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;