blocked for now, but it's a start

This commit is contained in:
Chris Kruining 2025-05-21 15:53:59 +02:00
parent c76c016a46
commit ec0ae60b10
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
13 changed files with 272 additions and 0 deletions

View file

@ -0,0 +1,5 @@
{
"provider_urls": [
"http://localhost:3000/fedcm.json"
]
}

26
public/fedcm.json Normal file
View file

@ -0,0 +1,26 @@
{
"accounts_endpoint": "/auth/idp/api/accounts",
"client_metadata_endpoint": "/auth/idp/api/metadata",
"id_assertion_endpoint": "/auth/idp/api/idtokens",
"disconnect_endpoint": "/auth/idp/api/disconnect",
"login_url": "/auth/idp",
"modes": {
"active": {
"supports_use_other_account": true
}
},
"branding": {
"background_color": "#6200ee",
"color": "#ffffff",
"icons": [
{
"url": "/images/favicon.dark.svg",
"size": 512
},
{
"url": "/images/favicon.light.svg",
"size": 512
}
]
}
}

View file

@ -0,0 +1 @@
Privacy Policy comes here.

View file

@ -0,0 +1 @@
Terms of Service comes here.

View file

@ -0,0 +1,95 @@
import { json, redirect } from "@solidjs/router";
import { APIEvent } from "@solidjs/start/server";
import { useSession } from "vinxi/http";
export type Middleware = (event: APIEvent) => Response | Promise<Response> | void | Promise<void> | Promise<void | Response>;
export interface User {
id: string;
username: string;
credential: string;
givenName: string;
familyName: string;
picture: string;
approvedClients: any[];
}
const USERS: User[] = [
{ id: '20d701f3-0f9f-4c21-a379-81b49f755f9e', username: 'chris', credential: 'test', givenName: 'Chris', familyName: 'Kruining', picture: '', approvedClients: [] },
{ id: '10199201-1564-47db-b67b-07088ff05de8', username: 'john', credential: 'test', givenName: 'John', familyName: 'Doe', picture: '', approvedClients: [] },
{ id: '633c44b3-8d3d-4dd1-8e1c-7de355d6dced', username: 'chris_alt', credential: 'test', givenName: 'Chris', familyName: 'Kruining', picture: '', approvedClients: [] },
{ id: 'b9759798-8a41-4961-94a6-feb2372de9cf', username: 'john_alt', credential: 'test', givenName: 'John', familyName: 'Doe', picture: '', approvedClients: [] },
];
export const getUser = (idOrUsername: string) => {
return USERS.find(u => u.id === idOrUsername || u.username === idOrUsername);
};
export const signIn = async (user: User) => {
const { update } = await useSession<{ signedIn?: boolean, id?: string }>({
password: process.env.SESSION_SECRET!,
});
await update({ signedIn: true, id: user.id });
};
export const signOut = async () => {
const { update } = await useSession<{ signedIn?: boolean, id?: string }>({
password: process.env.SESSION_SECRET!,
});
await update({});
};
export const use = (...middlewares: Middleware[]) => {
return (event: APIEvent) => {
for (const handler of middlewares) {
const response = handler(event);
if (response !== undefined) {
return response;
}
}
};
};
export const assertCsrf: Middleware = async ({ request }: APIEvent) => {
if (request.headers.get('Sec-Fetch-Dest') !== 'webidentity') {
console.log('request failed the csrf test');
return json({ error: 'Invalid access' }, { status: 400 });
}
};
export const assertSession: Middleware = async ({ request, locals }: APIEvent) => {
const user = await getSession();
if (user === undefined) {
console.log('user session not available');
return redirect('/auth/idp', { status: 401 });
}
locals.user = user;
};
export const assertApiSession = async ({ request, locals }: APIEvent) => {
const user = await getSession();
if (user === undefined) {
return json({ error: 'not signed in' }, { status: 401 });
}
locals.user = user;
};
const getSession = async () => {
const { data } = await useSession<{ signedIn?: boolean, id?: string }>({
password: process.env.SESSION_SECRET!,
});
if (data.signedIn !== true) {
return;
}
return USERS.find(u => u.id === data.id);
};

View file

@ -0,0 +1,29 @@
import { onMount } from "solid-js";
export default function Index() {
onMount(async () => {
try {
// navigator.login.setStatus('logged-in');
const credential = await navigator.credentials.get({
identity: {
providers: [{
configURL: new URL('http://localhost:3000/fedcm.json'),
clientId: '/auth/client',
nonce: 'kaas',
loginHint: 'chris',
}],
mode: 'passive',
context: undefined,
},
mediation: undefined,
});
console.log(credential);
} catch(e) {
console.error(e);
}
});
return 'WOOT';
}

View file

@ -0,0 +1,8 @@
import { json } from "@solidjs/router";
import { APIEvent } from "@solidjs/start/server";
export const GET = ({ request }: APIEvent) => {
console.error(`url not found ${request.url}`);
// return json({ error: `url ${request.url} is not implemented` }, { status: 404 })
};

View file

@ -0,0 +1,23 @@
import { json } from "@solidjs/router";
import { APIEvent } from "@solidjs/start/server";
import { assertApiSession, assertCsrf, use, User } from "~/features/auth";
export const GET = use(assertCsrf, assertApiSession, async (event: APIEvent) => {
const user = event.locals.user as User;
console.log('accounts endpoint', user);
return json({
accounts: [
{
id: user.id,
given_name: user.givenName,
name: `${user.givenName} ${user.familyName}`,
email: user.username,
picture: user.picture,
login_hints: [user.username],
approved_clients: user.approvedClients,
}
],
});
});

View file

@ -0,0 +1,11 @@
import { json } from "@solidjs/router";
import { APIEvent } from "@solidjs/start/server";
import { use, assertCsrf, assertApiSession } from "~/features/auth";
export const POST = use(assertCsrf, assertApiSession, async ({ request, locals }: APIEvent) => {
console.log(locals, request);
return json({
account_id: locals.user.id,
});
});

View file

@ -0,0 +1,11 @@
import { json } from "@solidjs/router";
import { APIEvent } from "@solidjs/start/server";
import { use, assertCsrf, assertApiSession } from "~/features/auth";
export const POST = use(assertCsrf, assertApiSession, async ({ request }: APIEvent) => {
console.log(request);
return json({
token: 'THIS IS A BEAUTIFUL TOKEN',
});
});

View file

@ -0,0 +1,35 @@
import { json, redirect } from "@solidjs/router";
import { APIEvent } from "@solidjs/start/server";
import { getUser, signIn } from "~/features/auth";
export const POST = async ({ request }: APIEvent) => {
const formData = await request.formData();
const username = formData.get('username');
const password = formData.get('password');
if (typeof username !== 'string' || /^[a-z0-9-_]+$/.test(username) !== true) {
return json({ error: 'Bad request' }, { status: 400 })
}
if (typeof password !== 'string' || password.length === 0) {
return json({ error: 'Bad request' }, { status: 400 })
}
const user = getUser(username);
if (user === undefined) {
return json({ error: 'Invalid credentials' }, { status: 400 });
}
if (user.credential !== password) {
return json({ error: 'Invalid credentials' }, { status: 400 });
}
await signIn(user);
return redirect('/auth/client', {
headers: {
'Set-Login': 'logged-in',
}
});
};

View file

@ -0,0 +1,10 @@
import { json } from "@solidjs/router";
import { APIEvent } from "@solidjs/start/server";
export const GET = ({ request }: APIEvent) => {
return json({
privacy_policy_url: '/privacy-policy.txt',
terms_of_service_url: '/terms-of-service.txt',
icons: []
});
};

View file

@ -0,0 +1,17 @@
import { useParams, useSearchParams } from "@solidjs/router"
export default function Login() {
const [params] = useSearchParams();
return <div>
<h1>Login</h1>
<form method="post" action="/auth/idp/api/login">
<label>username: <input type="text" name="username" value={params.login_hint} /></label>
<label>password: <input type="password" name="password" value="test" /></label>
<button type="submit">Submit</button>
</form>
</div>
}