UNPKG

the-wireguard-effect

Version:

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

263 lines 12.1 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 ParseResult from "effect/ParseResult"; import * as Schema from "effect/Schema"; import * as ini from "ini"; import * as InternetSchemas from "./InternetSchemas.js"; import * as WireguardKey from "./WireguardKey.js"; import * as internalWireguardPeer from "./internal/wireguardPeer.js"; /** * A wireguard peer configuration. * * @since 1.0.0 * @category Datatypes * @example * import * as Schema from "effect/Schema"; * import * as Duration from "effect/Duration"; * import * as Option from "effect/Option"; * import * as InternetSchemas from "the-wireguard-effect/InternetSchemas"; * 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(); * * // WireguardPeer * const peerDirectInstantiation = new WireguardPeer({ * PublicKey: publicKey, * AllowedIPs: [ * InternetSchemas.CidrBlock({ * ipv4: InternetSchemas.IPv4("192.168.0.0"), * mask: InternetSchemas.IPv4CidrMask(24), * }), * ], * Endpoint: InternetSchemas.Endpoint( * InternetSchemas.IPv4Endpoint({ * ip: InternetSchemas.IPv4("192.168.0.1"), * natPort: InternetSchemas.Port(51820), * listenPort: InternetSchemas.Port(51820), * }) * ), * PersistentKeepalive: Duration.seconds(20), * PresharedKey: Option.none(), * }); * * // Effect.Effect<WireguardPeer, ParseResult.ParseError, never> * const peerSchemaInstantiation = Schema.decode(WireguardPeer)({ * PublicKey: publicKey, * PresharedKey: preshareKey, * Endpoint: "192.168.0.1:51820", * AllowedIPs: ["192.168.0.0/24"], * PersistentKeepalive: Duration.seconds(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__*/Schema.optionalWith(InternetSchemas.DurationFromSeconds, { as: "Option", nullable: true }), /** * 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.optionalWith(/*#__PURE__*/Schema.ReadonlySetFromSelf(InternetSchemas.CidrBlockFromString), { nullable: true, default: () => 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__*/Schema.optionalWith(InternetSchemas.Endpoint, { nullable: true }), /** 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__*/Schema.optionalWith(WireguardKey.WireguardKey, { nullable: true, as: "Option" }), /** 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.compose(/*#__PURE__*/Schema.transform(Schema.Number, Schema.Number, { decode: Function.identity, encode: /*#__PURE__*/Number.multiply(1000) })), /*#__PURE__*/Schema.compose(Schema.DateTimeUtcFromNumber))) }) {} /** * A wireguard peer configuration encoded in INI format. * * @since 1.0.0 * @category Transformations * @example * 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.decode(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.encode(WireguardPeer.WireguardPeer)), * Effect.flatMap(Schema.decode(WireguardPeer.WireguardIniPeer)) * ); * * @see {@link WireguardPeer} */ export class WireguardIniPeer extends /*#__PURE__*/Schema.transformOrFail(WireguardPeer, Schema.String, { // The types really help make sure that everything in the output will be in the right format decode: (peer, _options, _ast) => { 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.fromNullable, 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 ParseResult.succeed(`[Peer]\n${publicKey}${presharedKey}${endpoint}${keepAlive}${aps}\n`); }, // Encoding is trivial using the ini library encode: (iniPeer, _options, _ast) => Function.pipe(iniPeer, ini.decode, data => data, ({ AllowedIPs, Endpoint, PersistentKeepalive, PresharedKey, PublicKey }) => ({ PublicKey, PresharedKey, Endpoint, PersistentKeepalive: Function.pipe(PersistentKeepalive, Option.fromNullable, Option.flatMap(Number.parse), Option.getOrUndefined), AllowedIPs: Function.pipe(AllowedIPs, Option.fromNullable, Option.map(aps => new Set(Array.isArray(aps) ? aps : [aps])), Option.getOrUndefined) }), Schema.decode(WireguardPeer, { onExcessProperty: "error" }), Effect.mapError(({ issue }) => issue)) }).annotations({ identifier: "WireguardIniPeer", description: "A wireguard ini peer configuration" }) {} /** * @since 1.0.0 * @category Schemas * @see https://www.wireguard.com/xplatform/ */ export class WireguardUapiSetPeer extends /*#__PURE__*/Schema.transformOrFail(WireguardPeer, Schema.String, { encode: (uapiPeer, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, uapiPeer, "Can not encode a UAPI set peer")), decode: (peer, _options, _ast) => { 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.fromNullable, 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 ParseResult.succeed(`${publicKey}${presharedKeyHex}${endpoint}${keepAlive}${aps.join("\n")}\n`); } }).annotations({ identifier: "WireguardUapiSetPeer", description: "A wireguard uapi peer configuration" }) {} /** * @since 1.0.0 * @category Schemas * @see https://www.wireguard.com/xplatform/ */ export class WireguardUapiGetPeer extends /*#__PURE__*/Schema.transformOrFail(Schema.String, WireguardPeer["uapi"], { encode: (uapiPeer, _options, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, uapiPeer, "Can not decode a UAPI get peer")), decode: 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.fromNullable, Option.map(hex => Buffer.from(hex, "hex").toString("base64")), Option.getOrUndefined); const allowedIps = Function.pipe(allowed_ip, Option.fromNullable, Option.map(aps => new Set(Array.isArray(aps) ? aps : [aps])), Option.getOrUndefined); const keepAlive = Function.pipe(persistent_keepalive_interval, Option.fromNullable, Option.flatMap(Number.parse), Option.getOrUndefined); return ParseResult.succeed({ rxBytes: rx_bytes, txBytes: tx_bytes, Endpoint: endpoint, PublicKey: publicKey, PresharedKey: presharedKey, lastHandshake: last_handshake_time_sec, AllowedIPs: allowedIps, PersistentKeepalive: keepAlive }); } }).annotations({ 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.lessThanOrEqualTo(Duration.seconds(30))), Effect.map(({ now, threshold }) => threshold(DateTime.distanceDuration(wireguardPeer.lastHandshake, now)))); //# sourceMappingURL=WireguardPeer.js.map