mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
jws encryption support (#74)
This commit is contained in:
@@ -5,7 +5,6 @@ import crypto from "crypto";
|
||||
import { Encryption } from "./encryption";
|
||||
import logger from "./logger";
|
||||
import { Clock, SystemClock } from "./clock";
|
||||
import { b64Encode, b64Decode } from "./b64";
|
||||
|
||||
type AccessToken = {
|
||||
value: string;
|
||||
@@ -60,14 +59,11 @@ export class EncryptedAccessTokens implements AccessTokens {
|
||||
this.encryption = encryption;
|
||||
}
|
||||
|
||||
mint = (authToken: string): string =>
|
||||
b64Encode(JSON.stringify(this.encryption.encrypt(authToken)));
|
||||
mint = (authToken: string): string => this.encryption.encrypt(authToken);
|
||||
|
||||
authTokenFor(value: string): string | undefined {
|
||||
try {
|
||||
return this.encryption.decrypt(
|
||||
JSON.parse(b64Decode(value))
|
||||
);
|
||||
return this.encryption.decrypt(value);
|
||||
} catch {
|
||||
logger.warn("Failed to decrypt access token...");
|
||||
return undefined;
|
||||
|
||||
@@ -16,7 +16,7 @@ import readConfig from "./config";
|
||||
import sonos, { bonobService } from "./sonos";
|
||||
import { MusicService } from "./music_service";
|
||||
import { SystemClock } from "./clock";
|
||||
import { jwtTokenSigner } from "./encryption";
|
||||
import { jwtSigner } from "./encryption";
|
||||
|
||||
const config = readConfig();
|
||||
|
||||
@@ -88,7 +88,7 @@ const app = server(
|
||||
applyContextPath: true,
|
||||
logRequests: true,
|
||||
version,
|
||||
tokenSigner: jwtTokenSigner(config.secret)
|
||||
tokenSigner: jwtSigner(config.secret)
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -5,10 +5,15 @@ import {
|
||||
createHash,
|
||||
} from "crypto";
|
||||
import jwt from "jsonwebtoken";
|
||||
import jws from "jws";
|
||||
|
||||
const ALGORITHM = "aes-256-cbc";
|
||||
const IV = randomBytes(16);
|
||||
|
||||
function isError(thing: any): thing is Error {
|
||||
return thing.name && thing.message
|
||||
}
|
||||
|
||||
export type Signer = {
|
||||
sign: (value: string) => string;
|
||||
verify: (token: string) => string;
|
||||
@@ -20,7 +25,8 @@ export const pSigner = (signer: Signer) => ({
|
||||
try {
|
||||
return resolve(signer.sign(value));
|
||||
} catch(e) {
|
||||
reject(`Failed to sign value: ${e}`);
|
||||
if(isError(e)) reject(e.message)
|
||||
else reject(`Failed to sign value: ${e}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -29,19 +35,20 @@ export const pSigner = (signer: Signer) => ({
|
||||
try {
|
||||
return resolve(signer.verify(token));
|
||||
}catch(e) {
|
||||
reject(`Failed to verify value: ${e}`);
|
||||
if(isError(e)) reject(e.message)
|
||||
else reject(`Failed to verify value: ${e}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const jwtTokenSigner = (secret: string) => ({
|
||||
export const jwtSigner = (secret: string) => ({
|
||||
sign: (value: string) => jwt.sign(value, secret),
|
||||
verify: (token: string) => {
|
||||
try {
|
||||
return jwt.verify(token, secret) as string;
|
||||
} catch (e) {
|
||||
throw `Failed to decode jwt, try re-authorising account`;
|
||||
throw new Error(`Failed to verify jwt, try re-authorising account within sonos app`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -52,11 +59,22 @@ export type Hash = {
|
||||
};
|
||||
|
||||
export type Encryption = {
|
||||
encrypt: (value: string) => Hash;
|
||||
decrypt: (hash: Hash) => string;
|
||||
encrypt: (value: string) => string;
|
||||
decrypt: (value: string) => string;
|
||||
};
|
||||
|
||||
const encryption = (secret: string): Encryption => {
|
||||
export const jwsEncryption = (secret: string): Encryption => {
|
||||
return {
|
||||
encrypt: (value: string) => jws.sign({
|
||||
header: { alg: 'HS256' },
|
||||
payload: value,
|
||||
secret: secret,
|
||||
}),
|
||||
decrypt: (value: string) => jws.decode(value).payload
|
||||
}
|
||||
}
|
||||
|
||||
export const cryptoEncryption = (secret: string): Encryption => {
|
||||
const key = createHash("sha256")
|
||||
.update(String(secret))
|
||||
.digest("base64")
|
||||
@@ -64,26 +82,26 @@ const encryption = (secret: string): Encryption => {
|
||||
return {
|
||||
encrypt: (value: string) => {
|
||||
const cipher = createCipheriv(ALGORITHM, key, IV);
|
||||
return {
|
||||
iv: IV.toString("hex"),
|
||||
encryptedData: Buffer.concat([
|
||||
cipher.update(value),
|
||||
cipher.final(),
|
||||
]).toString("hex"),
|
||||
};
|
||||
return `${IV.toString("hex")}.${Buffer.concat([
|
||||
cipher.update(value),
|
||||
cipher.final(),
|
||||
]).toString("hex")}`;
|
||||
},
|
||||
decrypt: (hash: Hash) => {
|
||||
decrypt: (value: string) => {
|
||||
const parts = value.split(".");
|
||||
if(parts.length != 2) throw `Invalid value to decrypt`;
|
||||
|
||||
const decipher = createDecipheriv(
|
||||
ALGORITHM,
|
||||
key,
|
||||
Buffer.from(hash.iv, "hex")
|
||||
Buffer.from(parts[0]!, "hex")
|
||||
);
|
||||
return Buffer.concat([
|
||||
decipher.update(Buffer.from(hash.encryptedData, "hex")),
|
||||
decipher.update(Buffer.from(parts[1]!, "hex")),
|
||||
decipher.final(),
|
||||
]).toString();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default encryption;
|
||||
export default jwsEncryption;
|
||||
|
||||
@@ -34,7 +34,7 @@ import { Icon, ICONS, festivals, features } from "./icon";
|
||||
import _, { shuffle } from "underscore";
|
||||
import morgan from "morgan";
|
||||
import { takeWithRepeats } from "./utils";
|
||||
import { jwtTokenSigner, Signer } from "./encryption";
|
||||
import { jwtSigner, Signer } from "./encryption";
|
||||
|
||||
export const BONOB_ACCESS_TOKEN_HEADER = "bat";
|
||||
|
||||
@@ -97,7 +97,7 @@ const DEFAULT_SERVER_OPTS: ServerOpts = {
|
||||
applyContextPath: true,
|
||||
logRequests: false,
|
||||
version: "v?",
|
||||
tokenSigner: jwtTokenSigner(`bonob-${uuid()}`),
|
||||
tokenSigner: jwtSigner(`bonob-${uuid()}`),
|
||||
};
|
||||
|
||||
function server(
|
||||
|
||||
Reference in New Issue
Block a user