identity/identity-api/index.js

222 lines
5.7 KiB
JavaScript
Raw Normal View History

import { createVerify } from "node:crypto"
2024-06-13 20:00:45 +00:00
import Fastify from 'fastify';
import * as Jose from 'jose';
import { v4 as uuidv4 } from 'uuid'
import assert from "node:assert";
2024-06-13 20:00:45 +00:00
const IDENTITY_API_LANDING_MESSAGE = 'identity-api v1.0.0';
const JWT_SECRET = new TextEncoder().encode(
'cc7e0d44fd473002f1c42167459001140ec6389b7353f8088f4d9a95f2f596f2',
)
const JWT_ALG = 'HS256'
const ASSET_API_ENDPOINT = "http://localhost:3001/"
const ASSET_API_PUBKEY = await loadAssetPubkey()
const ASSET_API_ALGORITHM = await loadAssetAlgo()
let session_keys = {
'uid:005d6417-a23c-48bd-b348-eafeae649b94': 'e381ba8c-e18a-4bca-afce-b212b37bc26b',
'key:e381ba8c-e18a-4bca-afce-b212b37bc26b': '005d6417-a23c-48bd-b348-eafeae649b94'
};
2024-06-13 20:00:45 +00:00
let users = {
2024-06-13 21:14:20 +00:00
'jane@identity.net': {
2024-06-13 20:00:45 +00:00
uid: '005d6417-a23c-48bd-b348-eafeae649b94',
2024-06-13 21:14:20 +00:00
password: '12345678901234567890',
name: 'Jane Doe',
2024-06-13 20:00:45 +00:00
}
}
const fastify = Fastify({
logger: true,
})
fastify.get('/', async (request, reply) => {
return IDENTITY_API_LANDING_MESSAGE;
})
fastify.post("/auth/account/fromkey", {
async handler(request, reply) {
if (!verifySignature(request.body, ASSET_API_PUBKEY)) {
reply.statusCode(401)
}
let body = JSON.parse(getContentFromSigned(request.body))
let uid = session_keys[`key:${body.session_key}`]
let user = Object.values(users).filter(v => v.uid === uid)
assert(user.length === 1)
user[0].password = undefined
return user[0]
}
})
fastify.get('/auth/genkey', {
async handler(request, reply) {
let jwt = request.headers['authorization'].replace('Bearer', '').trim()
let { payload } = await Jose.jwtVerify(jwt, JWT_SECRET)
let key = uuidv4()
session_keys[`uid:${payload.uid}`] = key
session_keys[`key:${key}`] = payload.uid
return {
session_key: session_keys[payload.uid]
}
},
schema: {
headers: {
type: 'object',
properties: {
authorization: { type: 'string' },
},
},
},
})
2024-06-13 21:48:12 +00:00
fastify.get('/auth/account', {
async handler(request, reply) {
let jwt = request.headers['authorization'].replace('Bearer', '').trim()
let { payload } = await Jose.jwtVerify(jwt, JWT_SECRET)
let user = users[payload.email]
user.password = undefined
return user
},
schema: {
headers: {
type: 'object',
properties: {
authorization: { type: 'string' },
},
},
},
})
2024-06-13 20:00:45 +00:00
fastify.post('/auth/login', {
async handler(request, reply) {
let user = users[request.body.email];
if (user != null && user.password == request.body.password) {
let token = await new Jose.SignJWT({
uid: user.uid,
2024-06-13 21:48:12 +00:00
email: request.body.email,
2024-06-13 20:00:45 +00:00
name: user.name,
})
.setProtectedHeader({ alg: JWT_ALG })
.setIssuedAt()
.setExpirationTime('4w')
.sign(JWT_SECRET);
return {
token,
}
}
reply.code(400);
return {
error: 'invalid credentials'
}
},
schema: {
body: {
type: 'object',
properties: {
email: { type: 'string' },
password: { type: 'string' },
},
},
},
})
fastify.post('/auth/register', {
async handler(request, reply) {
if (users[request.body.email] == null) {
users[request.body.email] = {
uid: uuidv4(),
password: request.body.password,
name: request.body.name,
}
let user = users[request.body.email]
let token = await new Jose.SignJWT({
uid: user.uid,
2024-06-13 21:48:12 +00:00
email: request.body.email,
2024-06-13 20:00:45 +00:00
name: user.name,
})
.setProtectedHeader({ alg: JWT_ALG })
.setIssuedAt()
.setExpirationTime('4w')
.sign(JWT_SECRET);
return { token }
}
reply.code(400);
return {
error: 'invalid data'
}
},
schema: {
body: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string' },
password: { type: 'string' },
},
},
},
})
fastify.listen({ port: 3000 })
async function loadAssetPubkey() {
let url = new URL(ASSET_API_ENDPOINT)
url.pathname = "/crypto/cert"
let res = await fetch(url)
return await res.text()
}
async function loadAssetAlgo() {
let url = new URL(ASSET_API_ENDPOINT)
url.pathname = "/crypto/algo"
let res = await fetch(url)
return await res.text()
}
/**
*
* @param {string} content
* @param {string} pubkeyText
*/
function verifySignature(content, pubkeyText) {
let parts = content
.replace("-----BEGIN SIGNED MESSAGE-----\n\n", "")
.replace("\n-----END SIGNATURE-----", "")
.split("\n\n-----BEGIN SIGNATURE-----\n\n")
assert(parts.length === 2);
let verify = createVerify(ASSET_API_ALGORITHM)
verify.update(parts[0])
let pubkey = Buffer.from(pubkeyText, "ascii")
let digest = Buffer.from(parts[1], "base64")
return verify.verify(pubkey, digest)
}
function getContentFromSigned(content) {
let parts = content
.replace("-----BEGIN SIGNED MESSAGE-----\n\n", "")
.replace("\n-----END SIGNATURE-----", "")
.split("\n\n-----BEGIN SIGNATURE-----\n\n")
assert(parts.length === 2);
return parts[0]
}