UNPKG

the-wireguard-effect

Version:

Cross platform wireguard api client for nodejs built on wireguard-go with effect-ts

240 lines 11.3 kB
/** * 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