Compare commits
2 commits
main
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
|
7d092176b2 | ||
|
ec0ae60b10 |
16 changed files with 342 additions and 15 deletions
|
@ -4,29 +4,14 @@ import devtools from 'solid-devtools/vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
vite: {
|
vite: {
|
||||||
resolve: {
|
|
||||||
alias: [
|
|
||||||
{ find: '@', replacement: 'F:\\Github\\calque\\node_modules\\' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
html: {
|
html: {
|
||||||
cspNonce: 'KAAS_IS_AWESOME',
|
cspNonce: 'KAAS_IS_AWESOME',
|
||||||
},
|
},
|
||||||
// css: {
|
|
||||||
// postcss: {
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
plugins: [
|
plugins: [
|
||||||
devtools({
|
devtools({
|
||||||
autoname: true,
|
autoname: true,
|
||||||
}),
|
}),
|
||||||
solidSvg(),
|
solidSvg(),
|
||||||
{
|
|
||||||
name: 'temp',
|
|
||||||
configResolved(config) {
|
|
||||||
console.log(config.resolve.alias);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
solid: {
|
solid: {
|
||||||
|
|
5
public/.well-known/web-identity
Normal file
5
public/.well-known/web-identity
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"provider_urls": [
|
||||||
|
"http://localhost:3000/auth/idp/api/config"
|
||||||
|
]
|
||||||
|
}
|
26
public/fedcm.json
Normal file
26
public/fedcm.json
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
1
public/privacy-policy.txt
Normal file
1
public/privacy-policy.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Privacy Policy comes here.
|
1
public/terms-of-service.txt
Normal file
1
public/terms-of-service.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Terms of Service comes here.
|
101
src/features/auth/index.ts
Normal file
101
src/features/auth/index.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
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: [ '/auth/client' ] },
|
||||||
|
{ id: '10199201-1564-47db-b67b-07088ff05de8', username: 'john', credential: 'test', givenName: 'John', familyName: 'Doe', picture: '', approvedClients: [ '/auth/client' ] },
|
||||||
|
{ id: '633c44b3-8d3d-4dd1-8e1c-7de355d6dced', username: 'chris_alt', credential: 'test', givenName: 'Chris', familyName: 'Kruining', picture: '', approvedClients: [ '/auth/client' ] },
|
||||||
|
{ id: 'b9759798-8a41-4961-94a6-feb2372de9cf', username: 'john_alt', credential: 'test', givenName: 'John', familyName: 'Doe', picture: '', approvedClients: [ '/auth/client' ] },
|
||||||
|
];
|
||||||
|
|
||||||
|
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 async (event: APIEvent) => {
|
||||||
|
console.log(`received ${event.request.url}`);
|
||||||
|
|
||||||
|
for (const handler of middlewares) {
|
||||||
|
const response = await handler(event);
|
||||||
|
|
||||||
|
console.log(response?.status);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
console.log('user session not available');
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
33
src/routes/auth/client/index.tsx
Normal file
33
src/routes/auth/client/index.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { onMount } from "solid-js";
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
onMount(async () => {
|
||||||
|
const user = await fetch('/auth/idp/api/user-info').then(r => r.json());
|
||||||
|
|
||||||
|
console.log(user);
|
||||||
|
|
||||||
|
if (user === undefined || true) {
|
||||||
|
try {
|
||||||
|
const credential = await navigator.credentials.get({
|
||||||
|
identity: {
|
||||||
|
providers: [{
|
||||||
|
configURL: new URL('http://localhost:3000/auth/idp/api/config'),
|
||||||
|
clientId: '/auth/client',
|
||||||
|
nonce: 'kaas',
|
||||||
|
loginHint: 'chris',
|
||||||
|
}],
|
||||||
|
mode: 'passive',
|
||||||
|
context: undefined,
|
||||||
|
},
|
||||||
|
mediation: 'silent',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(credential);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return 'WOOT';
|
||||||
|
}
|
8
src/routes/auth/idp/api/[...404].ts
Normal file
8
src/routes/auth/idp/api/[...404].ts
Normal 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 })
|
||||||
|
};
|
23
src/routes/auth/idp/api/accounts.ts
Normal file
23
src/routes/auth/idp/api/accounts.ts
Normal 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 ({ locals }: APIEvent) => {
|
||||||
|
const { user } = locals;
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
33
src/routes/auth/idp/api/config.ts
Normal file
33
src/routes/auth/idp/api/config.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { json } from "@solidjs/router";
|
||||||
|
import { APIEvent } from "@solidjs/start/server";
|
||||||
|
|
||||||
|
export const GET = async ({ request }: APIEvent) => {
|
||||||
|
console.log('config requested', request);
|
||||||
|
|
||||||
|
return json({
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
11
src/routes/auth/idp/api/disconnect.ts
Normal file
11
src/routes/auth/idp/api/disconnect.ts
Normal 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,
|
||||||
|
});
|
||||||
|
});
|
15
src/routes/auth/idp/api/idtokens.ts
Normal file
15
src/routes/auth/idp/api/idtokens.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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('id token requested', request.url);
|
||||||
|
|
||||||
|
return json({
|
||||||
|
token: 'THIS IS A BEAUTIFUL TOKEN',
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Set-Login': 'logged-in'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
39
src/routes/auth/idp/api/login.ts
Normal file
39
src/routes/auth/idp/api/login.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { json, redirect } from "@solidjs/router";
|
||||||
|
import { APIEvent } from "@solidjs/start/server";
|
||||||
|
import { getUser, signIn } from "~/features/auth";
|
||||||
|
|
||||||
|
export const POST = async ({ request }: APIEvent) => {
|
||||||
|
console.log('login requested', request.url);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
const token = 'THIS IS MY AWESOME TOKEN';
|
||||||
|
|
||||||
|
return json({ token }, {
|
||||||
|
headers: {
|
||||||
|
'Set-Login': 'logged-in',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
12
src/routes/auth/idp/api/metadata.ts
Normal file
12
src/routes/auth/idp/api/metadata.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { json } from "@solidjs/router";
|
||||||
|
import { APIEvent } from "@solidjs/start/server";
|
||||||
|
|
||||||
|
export const GET = ({ request }: APIEvent) => {
|
||||||
|
console.log('metadata requested', request.url);
|
||||||
|
|
||||||
|
return json({
|
||||||
|
privacy_policy_url: '/privacy-policy.txt',
|
||||||
|
terms_of_service_url: '/terms-of-service.txt',
|
||||||
|
icons: []
|
||||||
|
});
|
||||||
|
};
|
17
src/routes/auth/idp/api/user-info.ts
Normal file
17
src/routes/auth/idp/api/user-info.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { json } from "@solidjs/router";
|
||||||
|
import { APIEvent } from "@solidjs/start/server";
|
||||||
|
import { assertApiSession, use } from "~/features/auth";
|
||||||
|
|
||||||
|
export const GET = use(assertApiSession, async ({ locals }: APIEvent) => {
|
||||||
|
const { user } = locals;
|
||||||
|
|
||||||
|
return json({
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
});
|
17
src/routes/auth/idp/index.tsx
Normal file
17
src/routes/auth/idp/index.tsx
Normal 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>
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue