start implementing asset-api and m2m comms with identity-api
This commit is contained in:
parent
859844bcf4
commit
e566b16ea6
3 changed files with 161 additions and 3 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.keys/
|
||||||
|
.assets/
|
|
@ -1,13 +1,68 @@
|
||||||
|
import { readFileSync } from "node:fs"
|
||||||
|
import { createSign } from "node:crypto";
|
||||||
import Fastify from "fastify";
|
import Fastify from "fastify";
|
||||||
|
|
||||||
|
const { private: M2M_PRIVATE_KEY, public: M2M_PUBLIC_KEY } = loadM2MKeys()
|
||||||
|
const M2M_ALGORITHM = "RSA-SHA512"
|
||||||
|
|
||||||
|
const ASSETS_FOLDER = "~/.assets/"
|
||||||
const ASSET_API_LANDING_MESSAGE = "asset-api v1.0.0"
|
const ASSET_API_LANDING_MESSAGE = "asset-api v1.0.0"
|
||||||
|
|
||||||
|
const IDENTITY_API_ENDPOINT = "http://localhost:3000"
|
||||||
|
|
||||||
const fastify = new Fastify({
|
const fastify = new Fastify({
|
||||||
logger: true,
|
logger: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
fastify.get("/", async (request, reply) => {
|
fastify.get("/", async (request, reply) => {
|
||||||
return ASSET_API_LANDING_MESSAGE
|
return signString(ASSET_API_LANDING_MESSAGE)
|
||||||
})
|
})
|
||||||
|
|
||||||
fastify.listen({ port: 3001 })
|
fastify.get("/crypto/cert", async (request, reply) => {
|
||||||
|
return M2M_PUBLIC_KEY
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.get("/crypto/algo", (request, reply) => {
|
||||||
|
return M2M_ALGORITHM
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.get("/asset", {
|
||||||
|
async handler(request, reply) {
|
||||||
|
let url = new URL(IDENTITY_API_ENDPOINT)
|
||||||
|
url.pathname = "/auth/account/fromkey"
|
||||||
|
|
||||||
|
let res = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: signString(JSON.stringify({
|
||||||
|
session_key: request.query.session_key,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
return await res.text()
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
query: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
id: { type: "string" },
|
||||||
|
session_key: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["id", "session_key"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.listen({ port: 3001 })
|
||||||
|
|
||||||
|
function loadM2MKeys() {
|
||||||
|
return {
|
||||||
|
private: readFileSync("../.keys/m2m-dev.pem").toString("ascii"),
|
||||||
|
public: readFileSync("../.keys/m2m-dev.pub").toString("ascii"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function signString(content) {
|
||||||
|
let sign = createSign(M2M_ALGORITHM)
|
||||||
|
sign.update(content)
|
||||||
|
return `-----BEGIN SIGNED MESSAGE-----\n\n${content}\n\n-----BEGIN SIGNATURE-----\n\n${sign.sign(M2M_PRIVATE_KEY, "base64")}\n-----END SIGNATURE-----`
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { createVerify } from "node:crypto"
|
||||||
import Fastify from 'fastify';
|
import Fastify from 'fastify';
|
||||||
import * as Jose from 'jose';
|
import * as Jose from 'jose';
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import assert from "node:assert";
|
||||||
|
|
||||||
const IDENTITY_API_LANDING_MESSAGE = 'identity-api v1.0.0';
|
const IDENTITY_API_LANDING_MESSAGE = 'identity-api v1.0.0';
|
||||||
const JWT_SECRET = new TextEncoder().encode(
|
const JWT_SECRET = new TextEncoder().encode(
|
||||||
|
@ -8,6 +10,15 @@ const JWT_SECRET = new TextEncoder().encode(
|
||||||
)
|
)
|
||||||
const JWT_ALG = 'HS256'
|
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'
|
||||||
|
};
|
||||||
|
|
||||||
let users = {
|
let users = {
|
||||||
'jane@identity.net': {
|
'jane@identity.net': {
|
||||||
uid: '005d6417-a23c-48bd-b348-eafeae649b94',
|
uid: '005d6417-a23c-48bd-b348-eafeae649b94',
|
||||||
|
@ -24,6 +35,47 @@ fastify.get('/', async (request, reply) => {
|
||||||
return IDENTITY_API_LANDING_MESSAGE;
|
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' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
fastify.get('/auth/account', {
|
fastify.get('/auth/account', {
|
||||||
async handler(request, reply) {
|
async handler(request, reply) {
|
||||||
let jwt = request.headers['authorization'].replace('Bearer', '').trim()
|
let jwt = request.headers['authorization'].replace('Bearer', '').trim()
|
||||||
|
@ -118,4 +170,53 @@ fastify.post('/auth/register', {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
fastify.listen({ port: 3000 })
|
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]
|
||||||
|
}
|
Loading…
Reference in a new issue