the-wireguard-effect
Version:
Cross platform wireguard api client for nodejs built on wireguard-go with effect-ts
240 lines • 11.3 kB
JavaScript
/**
* Wireguard peer schema definitions
*
* @since 1.0.0
*/
import * as Array from "effect/Array";
import * as DateTime from "effect/DateTime";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Function from "effect/Function";
import * as Number from "effect/Number";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as SchemaGetter from "effect/SchemaGetter";
import * as SchemaIssue from "effect/SchemaIssue";
import * as InternetSchemas from "effect-schemas/Internet";
import * as ini from "ini";
import * as internalWireguardPeer from "./internal/wireguardPeer.js";
import * as WireguardInternetSchemas from "./InternetSchemas.js";
import * as WireguardKey from "./WireguardKey.js";
/**
* A wireguard peer configuration.
*
* @since 1.0.0
* @category Datatypes
* @example
* ```ts
*
* import * as Schema from "effect/Schema";
* import * as WireguardKey from "the-wireguard-effect/WireguardKey";
*
* import { WireguardPeer } from "the-wireguard-effect/WireguardPeer";
*
* const preshareKey = WireguardKey.generatePreshareKey();
* const { publicKey, privateKey: _privateKey } =
* WireguardKey.generateKeyPair();
*
* const peerSchemaInstantiation = Schema.decodeEffect(WireguardPeer)({
* PublicKey: publicKey,
* PresharedKey: preshareKey,
* Endpoint: "192.168.0.1:51820",
* AllowedIPs: new Set(["192.168.0.0/24"]),
* PersistentKeepalive: 20,
* });
* ```;
*/
export class WireguardPeer extends /*#__PURE__*/internalWireguardPeer.WireguardPeerConfigVariantSchema.Class("WireguardPeer")({
/**
* The persistent keepalive interval in seconds, 0 disables it. The
* difference between Option.none and Option.some(0) is important when
* performing something like a config update operation on an interface
* because Option.none will not include the keep alive setting in the update
* request, so it will remain the same as it was before the update, whereas
* Option.some(0) will disable the keep alive setting.
*/
PersistentKeepalive: /*#__PURE__*/WireguardInternetSchemas.DurationFromSeconds.pipe(Schema.OptionFromOptionalNullOr),
/**
* The collection of IPs/masks that will be accepted from this peer. If an
* identical value already exists as part of a prior peer, the allowed IP
* entry will be removed from that peer and added to this peer.
*/
AllowedIPs: /*#__PURE__*/Schema.ReadonlySet(InternetSchemas.CidrBlockFromString).pipe(/*#__PURE__*/Schema.withDecodingDefault(/*#__PURE__*/Effect.succeed(/*#__PURE__*/new Set([])))),
/**
* The value for this key is either IP:port for IPv4 or [IP]:port for IPv6
* or some.hostname.com:port for a hostname endpoint. The endpoint is
* optional and, if not supplied, the remote peer must connect first (so
* they should have the endpoint set to know where to connect to) and this
* client will just send requests back to the remote peer's source IP and
* port.
*/
Endpoint: /*#__PURE__*/WireguardInternetSchemas.Endpoint.pipe(Schema.NullOr, Schema.optional),
/** Lowercase hex-encoded public key of the new peer entry. */
PublicKey: WireguardKey.WireguardKey,
/**
* The preshared key is optional and is a lowercase hex-encoded key. The
* value may be an all zero string in the case of a set operation, in which
* case it indicates that the preshared-key should be removed.
*/
PresharedKey: /*#__PURE__*/WireguardKey.WireguardKey.pipe(Schema.OptionFromOptionalNullOr),
/** The number of received bytes. */
rxBytes: /*#__PURE__*/internalWireguardPeer.WireguardPeerConfigVariantSchema.FieldOnly(["uapi"])(Schema.NumberFromString),
/** The number of transmitted bytes. */
txBytes: /*#__PURE__*/internalWireguardPeer.WireguardPeerConfigVariantSchema.FieldOnly(["uapi"])(Schema.NumberFromString),
/**
* The number of seconds since the most recent handshake, expressed relative
* to the Unix epoch.
*/
lastHandshake: /*#__PURE__*/internalWireguardPeer.WireguardPeerConfigVariantSchema.FieldOnly(["uapi"])(/*#__PURE__*/Function.pipe(Schema.NumberFromString, /*#__PURE__*/Schema.decode({
decode: /*#__PURE__*/SchemaGetter.passthrough(),
encode: /*#__PURE__*/SchemaGetter.transform(/*#__PURE__*/Number.multiply(1000))
}), /*#__PURE__*/Schema.decodeTo(Schema.DateTimeUtcFromMillis)))
}) {}
/**
* A wireguard peer configuration encoded in INI format.
*
* @since 1.0.0
* @category Transformations
* @example
* ```ts
*
* import * as Effect from "effect/Effect";
* import * as Function from "effect/Function";
* import * as Schema from "effect/Schema";
* import * as WireguardKey from "the-wireguard-effect/WireguardKey";
* import * as WireguardPeer from "the-wireguard-effect/WireguardPeer";
*
* const preshareKey = WireguardKey.generatePreshareKey();
* const { publicKey, privateKey: _privateKey } =
* WireguardKey.generateKeyPair();
*
* const peer = Schema.decodeEffect(WireguardPeer.WireguardPeer)({
* PublicKey: publicKey,
* PresharedKey: preshareKey,
* AllowedIPs: new Set(["192.168.0.0/24"]),
* Endpoint: "192.168.0.1:51820",
* PersistentKeepalive: 20,
* });
*
* const iniPeer = Function.pipe(
* peer,
* Effect.flatMap(Schema.encodeEffect(WireguardPeer.WireguardPeer)),
* Effect.flatMap(Schema.decodeEffect(WireguardPeer.WireguardIniPeer))
* );
* ```;
*
* @see {@link WireguardPeer}
*/
export const WireguardIniPeer = /*#__PURE__*/WireguardPeer.pipe(/*#__PURE__*/Schema.decodeTo(Schema.String, {
// The types really help make sure that everything in the output will be in the right format
decode: /*#__PURE__*/SchemaGetter.transform(peer => {
const publicKey = `PublicKey = ${peer.PublicKey}\n`;
const presharedKey = Function.pipe(peer.PresharedKey, Option.map(key => `PresharedKey = ${key}\n`), Option.getOrElse(() => ""));
const endpoint = Function.pipe(peer.Endpoint, Option.fromNullishOr, Option.map(endpoint => {
const host = "address" in endpoint ? endpoint.address.ip : endpoint.host;
return `Endpoint = ${host}:${endpoint.natPort}\n`;
}), Option.getOrElse(() => ""));
const keepAlive = Function.pipe(peer.PersistentKeepalive, Option.map(keepalive => `PersistentKeepalive = ${Duration.toSeconds(keepalive)}\n`), Option.getOrElse(() => ""));
const aps = Function.pipe(peer.AllowedIPs, Array.fromIterable, Array.map(ap => `${ap.address.ip}/${ap.mask}`), Array.map(ap => `${ap}`), Array.join(", "), x => `AllowedIPs = ${x}`);
return `[Peer]\n${publicKey}${presharedKey}${endpoint}${keepAlive}${aps}\n`;
}),
// Encoding is trivial using the ini library
encode: /*#__PURE__*/SchemaGetter.transformOrFail(iniPeer => Function.pipe(iniPeer, ini.decode, data => data, ({
AllowedIPs,
Endpoint,
PersistentKeepalive,
PresharedKey,
PublicKey
}) => ({
PublicKey,
PresharedKey,
Endpoint,
PersistentKeepalive: Function.pipe(PersistentKeepalive, Option.fromNullishOr, Option.flatMap(Number.parse), Option.getOrUndefined),
AllowedIPs: Function.pipe(AllowedIPs, Option.fromNullishOr, Option.map(aps => new Set(Array.isArray(aps) ? aps : [aps])), Option.getOrUndefined)
}), Schema.decodeEffect(WireguardPeer, {
onExcessProperty: "error"
}), Effect.mapError(({
issue
}) => issue)))
}), /*#__PURE__*/Schema.annotate({
identifier: "WireguardIniPeer",
description: "A wireguard ini peer configuration"
}));
/**
* @since 1.0.0
* @category Schemas
* @see https://www.wireguard.com/xplatform/
*/
export const WireguardUapiSetPeer = /*#__PURE__*/WireguardPeer.pipe(/*#__PURE__*/Schema.decodeTo(Schema.String, {
encode: /*#__PURE__*/SchemaGetter.fail(input => new SchemaIssue.Forbidden(input, {
message: "Can not encode a UAPI set peer"
})),
decode: /*#__PURE__*/SchemaGetter.transform(peer => {
const publicKey = Function.pipe(peer.PublicKey, key => Buffer.from(key, "base64").toString("hex"), hex => `public_key=${hex}\n`);
const presharedKeyHex = Function.pipe(peer.PresharedKey, Option.map(key => Buffer.from(key, "base64").toString("hex")), Option.map(hex => `preshared_key=${hex}\n`), Option.getOrElse(() => ""));
const endpoint = Function.pipe(peer.Endpoint, Option.fromNullishOr, Option.map(endpoint => {
const host = "address" in endpoint ? endpoint.address.ip : endpoint.host;
return `endpoint=${host}:${endpoint.natPort}\n`;
}), Option.getOrElse(() => ""));
const keepAlive = Function.pipe(peer.PersistentKeepalive, Option.map(Duration.toSeconds), Option.map(seconds => `persistent_keepalive_interval=${seconds}\n`), Option.getOrElse(() => ""));
const aps = Function.pipe(peer.AllowedIPs, Array.fromIterable, Array.map(ap => `${ap.address.ip}/${ap.mask}`), Array.map(ap => `allowed_ip=${ap}`));
return `${publicKey}${presharedKeyHex}${endpoint}${keepAlive}${aps.join("\n")}\n`;
})
}), /*#__PURE__*/Schema.annotate({
identifier: "WireguardUapiSetPeer",
description: "A wireguard uapi peer configuration"
}));
/**
* @since 1.0.0
* @category Schemas
* @see https://www.wireguard.com/xplatform/
*/
export const WireguardUapiGetPeer = /*#__PURE__*/Schema.String.pipe(/*#__PURE__*/Schema.decodeTo(WireguardPeer["uapi"], {
encode: /*#__PURE__*/SchemaGetter.fail(input => new SchemaIssue.Forbidden(input, {
message: "Can not encode a UAPI get peer"
})),
decode: /*#__PURE__*/SchemaGetter.transform(uapiPeer => {
const data = ini.decode(uapiPeer);
const {
allowed_ip,
endpoint,
last_handshake_time_sec,
persistent_keepalive_interval,
preshared_key,
public_key,
rx_bytes,
tx_bytes
} = data;
const publicKey = Buffer.from(public_key, "hex").toString("base64");
const presharedKey = Function.pipe(preshared_key, Option.fromNullishOr, Option.map(hex => Buffer.from(hex, "hex").toString("base64")), Option.getOrUndefined);
const allowedIps = Function.pipe(allowed_ip, Option.fromNullishOr, Option.map(aps => new Set(Array.isArray(aps) ? aps : [aps])), Option.getOrUndefined);
const keepAlive = Function.pipe(persistent_keepalive_interval, Option.fromNullishOr, Option.flatMap(Number.parse), Option.getOrUndefined);
return {
rxBytes: rx_bytes,
txBytes: tx_bytes,
Endpoint: endpoint,
PublicKey: publicKey,
PresharedKey: presharedKey,
lastHandshake: last_handshake_time_sec,
AllowedIPs: allowedIps,
PersistentKeepalive: keepAlive
};
})
}), /*#__PURE__*/Schema.annotate({
identifier: "WireguardUapiGetPeer",
description: "A wireguard uapi peer configuration"
}));
/**
* @since 1.0.0
* @category Refinements
*/
export const hasBidirectionalTraffic = wireguardPeer => Effect.succeed(wireguardPeer.rxBytes > 0 && wireguardPeer.txBytes > 0);
/**
* @since 1.0.0
* @category Refinements
*/
export const hasHandshakedRecently = wireguardPeer => Effect.Do.pipe(Effect.bind("now", () => DateTime.now), Effect.let("threshold", () => Duration.isLessThanOrEqualTo(Duration.seconds(30))), Effect.map(({
now,
threshold
}) => threshold(DateTime.distance(wireguardPeer.lastHandshake, now))));
//# sourceMappingURL=WireguardPeer.js.map