Skip to content
Open
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
39 changes: 28 additions & 11 deletions idp-localhost.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,45 @@
"allowlisted_accounts": ["agektmr", "chromedemojp@gmail.com", "johnzoeller"],
"csp": {
"connect_src": [
"wss://idp.localhost",
"https://idp.localhost",
"wss://localhost:3000",
"wss://localhost:3001",
"https://sesame-identity-provider.appspot.com",
"https://rp.localhost",
"https://issuer.sgo.to",
"https://accounts.google.com/gsi/",
"https://accounts.sandbox.google.com",
"wss://localhost:3000",
"wss://localhost:3001"
"https://www.gstatic.com"
],
"font_src": [
"https://fonts.googleapis.com",
"https://fonts.gstatic.com"
],
"frame_src": [
"https://accounts.google.com/gsi/"
],
"img_src": [
"https://www.gravatar.com",
"https://gravatar.com",
"https://accounts.google.com/gsi-static",
"https://cdn.glitch.global",
"https://lh3.googleusercontent.com"
],
"font_src": ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
"frame_src": ["https://accounts.google.com/gsi/"],
"img_src": ["https://www.gravatar.com", "https://gravatar.com"],
"script_src": [
"https://idp.localhost",
"https://sesame-identity-provider.appspot.com",
"https://rp.localhost",
"https://accounts.google.com/gsi/client"
"https://idp.localhost",
"https://accounts.google.com/gsi/client",
"https://www.gstatic.com"
],
"style_src": [
"https://accounts.google.com/gsi/style",
"https://fonts.googleapis.com"
"https://fonts.googleapis.com",
"https://www.gstatic.com"
],
"style_src_elem": ["https://fonts.googleapis.com/icon"]
"style_src_elem": [
"https://fonts.googleapis.com",
"https://www.gstatic.com"
]
},
"supported_idps": [
{
Expand Down
1 change: 0 additions & 1 deletion idp.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"allowlisted_accounts": ["agektmr", "chromedemojp@gmail.com", "johnzoeller"],
"csp": {
"connect_src": [
"https://sesame-identity-provider.appspot.com",
"https://issuer.sgo.to",
"https://accounts.google.com/gsi/",
"https://accounts.sandbox.google.com",
Expand Down
33 changes: 24 additions & 9 deletions rp-localhost.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,42 @@
"wss://localhost:3000",
"wss://localhost:3001",
"https://sesame-identity-provider.appspot.com",
"wss://rp.localhost",
"https://rp.localhost",
"https://idp.localhost",
"https://issuer.sgo.to",
"https://accounts.google.com/gsi/",
"https://accounts.sandbox.google.com"
"https://accounts.sandbox.google.com",
"https://www.gstatic.com"
],
"font_src": [
"https://fonts.googleapis.com",
"https://fonts.gstatic.com"
],
"frame_src": [
"https://accounts.google.com/gsi/"
],
"img_src": [
"https://www.gravatar.com",
"https://gravatar.com",
"https://accounts.google.com/gsi-static",
"https://cdn.glitch.global",
"https://lh3.googleusercontent.com"
],
"font_src": ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
"frame_src": ["https://accounts.google.com/gsi/"],
"img_src": ["https://www.gravatar.com", "https://gravatar.com"],
"script_src": [
"https://sesame-identity-provider.appspot.com",
"https://rp.localhost",
"https://idp.localhost",
"https://accounts.google.com/gsi/client"
"https://accounts.google.com/gsi/client",
"https://www.gstatic.com"
],
"style_src": [
"https://accounts.google.com/gsi/style",
"https://fonts.googleapis.com"
"https://fonts.googleapis.com",
"https://www.gstatic.com"
],
"style_src_elem": ["https://fonts.googleapis.com/icon"]
"style_src_elem": [
"https://fonts.googleapis.com",
"https://www.gstatic.com"
]
},
"supported_idps": [
{
Expand Down
1 change: 0 additions & 1 deletion src/client/helpers/federated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/

import '~project-sesame/client/layout';
import {html, render} from 'lit';
import {toast, get} from '~project-sesame/client/helpers/index';
import {SesameIdP} from '~project-sesame/client/helpers/identity';
import {User} from '~project-sesame/server/libs/users';
Expand Down
50 changes: 45 additions & 5 deletions src/client/helpers/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import {$, post} from '~project-sesame/client/helpers/index';
import {saveFederation} from '~project-sesame/client/helpers/federated';

/**
* Options for FedCM authentication and delegation.
*/
export interface FedCmOptions {
mode?: 'active' | 'passive';
loginHint?: string;
Expand All @@ -32,20 +35,36 @@ export interface FedCmOptions {
// This is almost identical to the IdentityProvider class at https://sesame-identity-provider.appspot.com/fedcm.js.
// Copied here since some integration needs custom implementation on the RP side.
// ex: unified auth with password, multiple IdPs, etc.
/**
* Helper class for interacting with the Identity Provider (IdP) via FedCM.
* This class handles initialization, sign-in, and attribute delegation.
*/
export class SesameIdP {
/** List of IdP URLs to initialize with. */
urls: string[] = [];

/** List of resolved IdP configurations. */
idps: {
origin: string;
configURL: string;
clientId: string;
}[] = [];

/**
* Creates an instance of SesameIdP.
* @param urls List of IdP URLs to initialize with.
*/
constructor(urls: string[] = []) {
for (const url of urls) {
this.urls.push(new URL(url).toString());
}
}

/**
* Initializes the IdP by fetching configuration options from the server.
* Resolves the config URLs and client IDs for each IdP.
* @returns The nonce to be used for authentication.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New hint has been added for a return value, but the function does not return it.

*/
async initialize() {
const options = await post('/federation/options', {
urls: this.urls,
Expand All @@ -63,9 +82,14 @@ export class SesameIdP {
};
this.idps.push(idp);
}
return options.nonce;
return;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a return value here? If not, just remove the return.

}

/**
* Performs the FedCM sign-in flow.
* @param options Configuration options for the sign-in request.
* @returns A promise that resolves to the verified user object or undefined.
*/
async signIn(
options: FedCmOptions = {}
// @ts-ignore
Expand All @@ -91,10 +115,9 @@ export class SesameIdP {
providers.push({
configURL: idp.configURL,
clientId: idp.clientId,
nonce,
loginHint,
fields,
params,
params: { nonce, ...params },
});
}

Expand All @@ -121,6 +144,11 @@ export class SesameIdP {
}
}

/**
* Performs the FedCM delegation flow (Verifiable Credentials / SD-JWT).
* @param options Configuration options for the delegation request.
* @returns A promise that resolves to the verified token or undefined.
*/
async delegate(options: FedCmOptions = {}): Promise<string | undefined> {
let {mode = '', nonce, fields, mediation, params = {}} = options;
if (!nonce) {
Expand All @@ -136,9 +164,8 @@ export class SesameIdP {
format: 'vc+sd-jwt',
configURL: idp.configURL,
clientId: idp.clientId,
nonce,
fields,
params,
params: { nonce, ...params },
});
}

Expand All @@ -151,6 +178,11 @@ export class SesameIdP {
return await this.verifySdJwt(cred);
}

/**
* Verifies the ID token with the backend and saves the federation status.
* @param cred The IdentityCredential returned by navigator.credentials.get.
* @returns The verified user object.
*/
// @ts-ignore
private async verifyIdToken(cred: IdentityCredential): User {
const idp = this.idps.find(idp => {
Expand All @@ -171,6 +203,11 @@ export class SesameIdP {
return user;
}

/**
* Verifies the SD-JWT with the backend.
* @param cred The IdentityCredential returned by navigator.credentials.get.
* @returns The verified user object or token.
*/
// @ts-ignore
private async verifySdJwt(cred: IdentityCredential): User {
const idp = this.idps.find(idp => {
Expand All @@ -189,6 +226,9 @@ export class SesameIdP {
});
return user;
}
/**
* Signs out the user by preventing silent access.
*/
async signOut() {
if (navigator.credentials && navigator.credentials.preventSilentAccess) {
await navigator.credentials.preventSilentAccess();
Expand Down
35 changes: 18 additions & 17 deletions src/client/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,28 +274,29 @@ export function postForm(callback: Function, errorCallback: Function): void {
form.addEventListener('submit', async (s: SubmitEvent) => {
s.preventDefault();
const form = new FormData(<HTMLFormElement>s.target);

// If `PasswordCredential` is supported, store it to the password manager.
// @ts-ignore
if (window.PasswordCredential) {
// @ts-ignore
const id = form.get('username');
const password = form.get('password');
// Save only if `id` and `password` combination is being submitted.
if (id && password) {
await navigator.credentials.create({
// @ts-ignore
password: {id, password},
});
console.log('PasswordCredential stored');
}
}

const cred = {} as {[key: string]: FormDataEntryValue};
form.forEach((v, k) => (cred[k] = v));
loading.start();

try {
const results = await post((<HTMLFormElement>s.target).action, cred);

// If `PasswordCredential` is supported, store it to the password manager.
// @ts-ignore
if (window.PasswordCredential) {
// @ts-ignore
const id = form.get('username');
const password = form.get('password');
// Save only if `id` and `password` combination is being submitted.
if (id && password) {
await navigator.credentials.create({
// @ts-ignore
password: {id, password},
});
console.log('PasswordCredential stored');
}
}

callback(results);
} catch (e: any) {
loading.stop();
Expand Down
14 changes: 8 additions & 6 deletions src/client/pages/fedcm-active-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ import '~project-sesame/client/layout';
import {$, toast, redirect} from '~project-sesame/client/helpers/index';
import {SesameIdP} from '~project-sesame/client/helpers/identity';

const fedcm = new SesameIdP(['https://sesame-identity-provider.appspot.com']);
let fedcm;
if (location.host.indexOf('rp.localhost') > -1) {
fedcm = new SesameIdP(['https://idp.localhost']);
} else {
fedcm = new SesameIdP(['https://identity-provider-sesame.appspot.com']);
}
await fedcm.initialize();
const google = new SesameIdP(['https://accounts.google.com']);
// `.initialize()` function returns a `nonce`. Since there are multiple
// `.initialize()` functions called in this page, only the last one will be
// recorded in the session. Use the last `nonce` for all `.signIn()` functions.
const nonce = await google.initialize();
await google.initialize();

const signIn = async (idp: SesameIdP) => {
try {
await idp.signIn({mode: 'active', nonce});
await idp.signIn({ mode: 'active' });
await redirect('/home');
} catch (e: any) {
console.error(e);
Expand Down
3 changes: 1 addition & 2 deletions src/client/pages/fedcm-delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,11 @@ if (window.IdentityCredential) {
'https://accounts.sandbox.google.com',
'https://issuer.sgo.to',
]);
const nonce = await idp.initialize();
await idp.initialize();
await idp.delegate({
fields: ['name', 'email', 'picture'],
// @ts-ignore
mediation: 'conditional',
nonce,
});
await redirect('/home');
} catch (error: any) {
Expand Down
4 changes: 2 additions & 2 deletions src/client/pages/fedcm-form-autofill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ async function fedcm() {
const idp = new SesameIdP([
'https://accounts.google.com',
'https://sesame-identity-provider.appspot.com',
'https://idp.localhost',
'https://issuer.sgo.to',
]);
const nonce = await idp.initialize();
await idp.initialize();
await idp.signIn({
// @ts-ignore
mediation: 'conditional',
nonce,
});
await redirect('/home');
} catch (error: any) {
Expand Down
4 changes: 2 additions & 2 deletions src/client/pages/fedcm-passive-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ if ('IdentityCredential' in window) {
try {
const idpURLs = await getIdpUrls();
const idp = new SesameIdP(idpURLs);
const nonce = await idp.initialize();
await idp.signIn({mode: 'passive', mediation: 'required', nonce});
await idp.initialize();
await idp.signIn({ mode: 'passive', mediation: 'required' });
await redirect('/home');
} catch (e: any) {
console.error(e);
Expand Down
1 change: 0 additions & 1 deletion src/client/pages/password-reauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import '~project-sesame/client/layout';
import {
$,
redirect,
postForm,
toast,
Expand Down
Loading