Bring in apple client into monorepo (#1737)

This PR brings in the apple client into the monorepo.

---------

Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
This commit is contained in:
Roopesh Chander
2023-07-07 23:07:24 +05:30
committed by GitHub
parent e23dbeab60
commit c9c13e1e11
64 changed files with 3748 additions and 12 deletions

View File

@@ -1,3 +1,3 @@
[codespell]
skip = ./rust/target,Cargo.lock,./www/docs/reference/api/*.mdx,./erl_crash.dump,./apps/*/erl_crash.dump,./cover,./vendor,*.json,yarn.lock,seeds.exs,./**/node_modules,./deps,./priv/static,./priv/plts,./**/priv/static,./.git,./www/build,./_build
ignore-words-list = crate,keypair,keypairs,iif,statics,wee,anull,commitish
ignore-words-list = crate,keypair,keypairs,iif,statics,wee,anull,commitish,inout

View File

@@ -3,6 +3,7 @@ on:
pull_request:
paths:
- "swift/**"
- "rust/connlib/**"
- ".github/workflows/swift.yml"
merge_group:
types: [checks_requested]
@@ -26,8 +27,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# TODO: Add a basic CI for the Apple client
# See rust.yml how we build, package and release connlib as an example
build:
runs-on: macos-latest
permissions:
@@ -39,4 +38,45 @@ jobs:
- draft-release
steps:
- uses: actions/checkout@v3
# TODO: Build Apple client from the CLI
- run: rustup show
- uses: Swatinem/rust-cache@v2
with:
workspaces: ./rust
prefix-key: rust-${{ matrix.runs-on }}
save-if: ${{ github.ref == 'refs/heads/cloud' }}
- name: Update toolchain
run: rustup show
- name: Setup lipo
run: cargo install cargo-lipo
- uses: actions/cache@v3
with:
path: apple/.build
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: Build Connlib.xcframework
env:
CONFIGURATION: Release
PROJECT_DIR: .
working-directory: ./rust/connlib/clients/apple
run: |
# build-xcframework.sh calls build-rust.sh indirectly via `xcodebuild`, but it pollutes the environment
# to the point that it causes the `ring` build to fail for the aarch64-apple-darwin target. So, explicitly
# build first. See https://github.com/briansmith/ring/issues/1332
PLATFORM_NAME=macosx ./build-rust.sh
PLATFORM_NAME=iphoneos ./build-rust.sh
./build-xcframework-dev.sh
- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Build app for macOS
working-directory: ./swift/apple
run: |
cp Firezone/Developer.xcconfig.ci-macOS Firezone/Developer.xcconfig
xcodebuild build -scheme Firezone -sdk macosx -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO
- name: Build app for iOS
working-directory: ./swift/apple
run: |
cp Firezone/Developer.xcconfig.ci-iOS Firezone/Developer.xcconfig
xcodebuild build -scheme Firezone -sdk iphoneos -destination 'generic/platform=iOS' CODE_SIGNING_ALLOWED=NO

View File

@@ -6,13 +6,6 @@
set -ex
if [[ -z "$PROJECT_DIR" ]]; then
echo "Must provide PROJECT_DIR environment variable set to the Xcode project directory." 1>&2
exit 1
fi
cd $PROJECT_DIR
# Default PLATFORM_NAME to macosx if not set.
: "${PLATFORM_NAME:=macosx}"

8
swift/apple/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.DS_Store
.build/
build/
DerivedData/
xcuserdata/
**/*.xcuserstate
Firezone/Developer.xcconfig

13
swift/apple/.swiftformat Normal file
View File

@@ -0,0 +1,13 @@
--swiftversion 5.7
--binarygrouping none
--decimalgrouping none
--hexgrouping none
--indent 2
--octalgrouping none
--semicolons never
--wraparguments before-first
--wrapcollections before-first
--wrapparameters before-first
--extensionacl on-declarations
--maxwidth 100
--header \n {file}\n (c) {created.year} Firezone, Inc.\n LICENSE: Apache-2.0\n

View File

@@ -0,0 +1,797 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
05CF1CF1290B1CEE00CF4755 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */; };
05CF1CF9290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 05CF1CF0290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
05CF1D04290B1DCD00CF4755 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */; platformFilters = (macos, ); };
05CF1D0C290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 05CF1D03290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex */; platformFilters = (macos, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
05CF1D16290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */; };
05CF1D17290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */; };
05D3BB2128FDE9C000BC3727 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */; };
794C38152970A2660029F38F /* FirezoneKit in Frameworks */ = {isa = PBXBuildFile; productRef = 794C38142970A2660029F38F /* FirezoneKit */; };
794C38172970A26A0029F38F /* FirezoneKit in Frameworks */ = {isa = PBXBuildFile; productRef = 794C38162970A26A0029F38F /* FirezoneKit */; };
79756C6629704A7A0018E2D5 /* FirezoneKit in Frameworks */ = {isa = PBXBuildFile; productRef = 79756C6529704A7A0018E2D5 /* FirezoneKit */; };
8DCC021D28D512AC007E12D2 /* FirezoneApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DCC021C28D512AC007E12D2 /* FirezoneApp.swift */; };
8DCC022628D512AE007E12D2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8DCC022528D512AE007E12D2 /* Assets.xcassets */; };
8DCC022A28D512AE007E12D2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8DCC022928D512AE007E12D2 /* Preview Assets.xcassets */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
05CF1CF7290B1CEE00CF4755 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 8DCC021128D512AC007E12D2 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 05CF1CEF290B1CEE00CF4755;
remoteInfo = FirezoneNetworkExtensioniOS;
};
05CF1D0A290B1DCD00CF4755 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 8DCC021128D512AC007E12D2 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 05CF1D02290B1DCD00CF4755;
remoteInfo = FirezoneNetworkExtensionmacOS;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
0556189428EF883500DF9E3C /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
05CF1CF9290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex in Embed Foundation Extensions */,
05CF1D0C290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
05CF1C8A290B11A500CF4755 /* Embed System Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "$(SYSTEM_EXTENSIONS_FOLDER_PATH)";
dstSubfolderSpec = 16;
files = (
);
name = "Embed System Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
05B6467B292C36140014A4D4 /* AuthenticationServiceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceStub.swift; sourceTree = "<group>"; };
05CF1C39290995DA00CF4755 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
05CF1CDE290B1A9000CF4755 /* FirezoneNetworkExtension_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FirezoneNetworkExtension_macOS.entitlements; sourceTree = "<group>"; };
05CF1CF0290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FirezoneNetworkExtensioniOS.appex; sourceTree = BUILT_PRODUCTS_DIR; };
05CF1CF6290B1CEE00CF4755 /* FirezoneNetworkExtension_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FirezoneNetworkExtension_iOS.entitlements; sourceTree = "<group>"; };
05CF1D03290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FirezoneNetworkExtensionmacOS.appex; sourceTree = BUILT_PRODUCTS_DIR; };
05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
79BA2AF229A00F8800A2E6DC /* Developer.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Developer.xcconfig; sourceTree = "<group>"; };
79C88B24296F494500261800 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
8DCC021928D512AC007E12D2 /* Firezone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Firezone.app; sourceTree = BUILT_PRODUCTS_DIR; };
8DCC021C28D512AC007E12D2 /* FirezoneApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirezoneApp.swift; sourceTree = "<group>"; };
8DCC022528D512AE007E12D2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
8DCC022728D512AE007E12D2 /* Firezone.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Firezone.entitlements; sourceTree = "<group>"; };
8DCC022928D512AE007E12D2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
8DD2C4C3297B37BA00F984BF /* FirezoneKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = FirezoneKit; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
05CF1CED290B1CEE00CF4755 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
794C38152970A2660029F38F /* FirezoneKit in Frameworks */,
05CF1CF1290B1CEE00CF4755 /* NetworkExtension.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
05CF1D00290B1DCD00CF4755 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
794C38172970A26A0029F38F /* FirezoneKit in Frameworks */,
05CF1D04290B1DCD00CF4755 /* NetworkExtension.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
8DCC021628D512AC007E12D2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
05D3BB2128FDE9C000BC3727 /* NetworkExtension.framework in Frameworks */,
79756C6629704A7A0018E2D5 /* FirezoneKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0580451B29385C150080D1F0 /* Recovered References */ = {
isa = PBXGroup;
children = (
05B6467B292C36140014A4D4 /* AuthenticationServiceStub.swift */,
);
name = "Recovered References";
sourceTree = "<group>";
};
05833DF928F73B070008FAB0 /* FirezoneNetworkExtension */ = {
isa = PBXGroup;
children = (
05CF1CF6290B1CEE00CF4755 /* FirezoneNetworkExtension_iOS.entitlements */,
05CF1CDE290B1A9000CF4755 /* FirezoneNetworkExtension_macOS.entitlements */,
05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */,
);
path = FirezoneNetworkExtension;
sourceTree = "<group>";
};
05E1505F28FF398000170F82 /* Application */ = {
isa = PBXGroup;
children = (
8DCC021C28D512AC007E12D2 /* FirezoneApp.swift */,
);
path = Application;
sourceTree = "<group>";
};
8D3F90C328D64FAD00980124 /* Frameworks */ = {
isa = PBXGroup;
children = (
05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
8DCC021028D512AC007E12D2 = {
isa = PBXGroup;
children = (
8DD2C4C2297B37BA00F984BF /* Packages */,
8DCC021B28D512AC007E12D2 /* Firezone */,
05833DF928F73B070008FAB0 /* FirezoneNetworkExtension */,
8DCC021A28D512AC007E12D2 /* Products */,
8D3F90C328D64FAD00980124 /* Frameworks */,
0580451B29385C150080D1F0 /* Recovered References */,
);
sourceTree = "<group>";
};
8DCC021A28D512AC007E12D2 /* Products */ = {
isa = PBXGroup;
children = (
8DCC021928D512AC007E12D2 /* Firezone.app */,
05CF1CF0290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex */,
05CF1D03290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex */,
);
name = Products;
sourceTree = "<group>";
};
8DCC021B28D512AC007E12D2 /* Firezone */ = {
isa = PBXGroup;
children = (
05E1505F28FF398000170F82 /* Application */,
8DCC022528D512AE007E12D2 /* Assets.xcassets */,
79C88B24296F494500261800 /* Config.xcconfig */,
79BA2AF229A00F8800A2E6DC /* Developer.xcconfig */,
8DCC022728D512AE007E12D2 /* Firezone.entitlements */,
05CF1C39290995DA00CF4755 /* Info.plist */,
8DCC022828D512AE007E12D2 /* Preview Content */,
);
path = Firezone;
sourceTree = "<group>";
};
8DCC022828D512AE007E12D2 /* Preview Content */ = {
isa = PBXGroup;
children = (
8DCC022928D512AE007E12D2 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
8DD2C4C2297B37BA00F984BF /* Packages */ = {
isa = PBXGroup;
children = (
8DD2C4C3297B37BA00F984BF /* FirezoneKit */,
);
name = Packages;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
05CF1CEF290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 05CF1CFA290B1CEE00CF4755 /* Build configuration list for PBXNativeTarget "FirezoneNetworkExtensioniOS" */;
buildPhases = (
05CF1CEC290B1CEE00CF4755 /* Sources */,
05CF1CED290B1CEE00CF4755 /* Frameworks */,
05CF1CEE290B1CEE00CF4755 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = FirezoneNetworkExtensioniOS;
packageProductDependencies = (
794C38142970A2660029F38F /* FirezoneKit */,
);
productName = FirezoneNetworkExtensioniOS;
productReference = 05CF1CF0290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex */;
productType = "com.apple.product-type.app-extension";
};
05CF1D02290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 05CF1D0D290B1DCD00CF4755 /* Build configuration list for PBXNativeTarget "FirezoneNetworkExtensionmacOS" */;
buildPhases = (
05CF1CFF290B1DCD00CF4755 /* Sources */,
05CF1D00290B1DCD00CF4755 /* Frameworks */,
05CF1D01290B1DCD00CF4755 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = FirezoneNetworkExtensionmacOS;
packageProductDependencies = (
794C38162970A26A0029F38F /* FirezoneKit */,
);
productName = FirezoneNetworkExtensionmacOS;
productReference = 05CF1D03290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex */;
productType = "com.apple.product-type.app-extension";
};
8DCC021828D512AC007E12D2 /* Firezone */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8DCC024328D512AE007E12D2 /* Build configuration list for PBXNativeTarget "Firezone" */;
buildPhases = (
8DCC021528D512AC007E12D2 /* Sources */,
8DCC021628D512AC007E12D2 /* Frameworks */,
8DCC021728D512AC007E12D2 /* Resources */,
0556189428EF883500DF9E3C /* Embed Foundation Extensions */,
05CF1C8A290B11A500CF4755 /* Embed System Extensions */,
);
buildRules = (
);
dependencies = (
05CF1CF8290B1CEE00CF4755 /* PBXTargetDependency */,
05CF1D0B290B1DCD00CF4755 /* PBXTargetDependency */,
);
name = Firezone;
packageProductDependencies = (
79756C6529704A7A0018E2D5 /* FirezoneKit */,
);
productName = Firezone;
productReference = 8DCC021928D512AC007E12D2 /* Firezone.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
8DCC021128D512AC007E12D2 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1400;
LastUpgradeCheck = 1420;
TargetAttributes = {
05CF1CEF290B1CEE00CF4755 = {
CreatedOnToolsVersion = 14.0.1;
};
05CF1D02290B1DCD00CF4755 = {
CreatedOnToolsVersion = 14.0.1;
};
8DCC021828D512AC007E12D2 = {
CreatedOnToolsVersion = 14.0;
LastSwiftMigration = 1400;
};
};
};
buildConfigurationList = 8DCC021428D512AC007E12D2 /* Build configuration list for PBXProject "Firezone" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8DCC021028D512AC007E12D2;
packageReferences = (
);
productRefGroup = 8DCC021A28D512AC007E12D2 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8DCC021828D512AC007E12D2 /* Firezone */,
05CF1CEF290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS */,
05CF1D02290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
05CF1CEE290B1CEE00CF4755 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
05CF1D01290B1DCD00CF4755 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
8DCC021728D512AC007E12D2 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8DCC022A28D512AE007E12D2 /* Preview Assets.xcassets in Resources */,
8DCC022628D512AE007E12D2 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
05CF1CEC290B1CEE00CF4755 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
05CF1D17290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
05CF1CFF290B1DCD00CF4755 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
05CF1D16290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
8DCC021528D512AC007E12D2 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8DCC021D28D512AC007E12D2 /* FirezoneApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
05CF1CF8290B1CEE00CF4755 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilter = ios;
target = 05CF1CEF290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS */;
targetProxy = 05CF1CF7290B1CEE00CF4755 /* PBXContainerItemProxy */;
};
05CF1D0B290B1DCD00CF4755 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilters = (
macos,
);
target = 05CF1D02290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS */;
targetProxy = 05CF1D0A290B1DCD00CF4755 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
05CF1CFB290B1CEE00CF4755 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = FirezoneNetworkExtension/FirezoneNetworkExtension_iOS.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
FRAMEWORK_SEARCH_PATHS = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FirezoneNetworkExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FirezoneNetworkExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
MARKETING_VERSION = 1.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TVOS_DEPLOYMENT_TARGET = "";
WATCHOS_DEPLOYMENT_TARGET = "";
};
name = Debug;
};
05CF1CFC290B1CEE00CF4755 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = FirezoneNetworkExtension/FirezoneNetworkExtension_iOS.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
FRAMEWORK_SEARCH_PATHS = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FirezoneNetworkExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FirezoneNetworkExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TVOS_DEPLOYMENT_TARGET = "";
VALIDATE_PRODUCT = YES;
WATCHOS_DEPLOYMENT_TARGET = "";
};
name = Release;
};
05CF1D0E290B1DCD00CF4755 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = FirezoneNetworkExtension/FirezoneNetworkExtension_macOS.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FirezoneNetworkExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FirezoneNetworkExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = "";
WATCHOS_DEPLOYMENT_TARGET = "";
};
name = Debug;
};
05CF1D0F290B1DCD00CF4755 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = FirezoneNetworkExtension/FirezoneNetworkExtension_macOS.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FirezoneNetworkExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FirezoneNetworkExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = "";
WATCHOS_DEPLOYMENT_TARGET = "";
};
name = Release;
};
8DCC024128D512AE007E12D2 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 79C88B24296F494500261800 /* Config.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8DCC024228D512AE007E12D2 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 79C88B24296F494500261800 /* Config.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8DCC024428D512AE007E12D2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Firezone/Firezone.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEFINES_MODULE = NO;
DEVELOPMENT_ASSET_PATHS = "\"Firezone/Preview Content\"";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Firezone/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Firezone;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_LSUIElement = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MARKETING_VERSION = 1.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INSTALL_MODULE = YES;
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TVOS_DEPLOYMENT_TARGET = "";
WATCHOS_DEPLOYMENT_TARGET = "";
};
name = Debug;
};
8DCC024528D512AE007E12D2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Firezone/Firezone.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
DEFINES_MODULE = NO;
DEVELOPMENT_ASSET_PATHS = "\"Firezone/Preview Content\"";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Firezone/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Firezone;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_LSUIElement = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MARKETING_VERSION = 1.0;
PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INSTALL_MODULE = NO;
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TVOS_DEPLOYMENT_TARGET = "";
WATCHOS_DEPLOYMENT_TARGET = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
05CF1CFA290B1CEE00CF4755 /* Build configuration list for PBXNativeTarget "FirezoneNetworkExtensioniOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05CF1CFB290B1CEE00CF4755 /* Debug */,
05CF1CFC290B1CEE00CF4755 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
05CF1D0D290B1DCD00CF4755 /* Build configuration list for PBXNativeTarget "FirezoneNetworkExtensionmacOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05CF1D0E290B1DCD00CF4755 /* Debug */,
05CF1D0F290B1DCD00CF4755 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8DCC021428D512AC007E12D2 /* Build configuration list for PBXProject "Firezone" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8DCC024128D512AE007E12D2 /* Debug */,
8DCC024228D512AE007E12D2 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8DCC024328D512AE007E12D2 /* Build configuration list for PBXNativeTarget "Firezone" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8DCC024428D512AE007E12D2 /* Debug */,
8DCC024528D512AE007E12D2 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */
794C38142970A2660029F38F /* FirezoneKit */ = {
isa = XCSwiftPackageProductDependency;
productName = FirezoneKit;
};
794C38162970A26A0029F38F /* FirezoneKit */ = {
isa = XCSwiftPackageProductDependency;
productName = FirezoneKit;
};
79756C6529704A7A0018E2D5 /* FirezoneKit */ = {
isa = XCSwiftPackageProductDependency;
productName = FirezoneKit;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 8DCC021128D512AC007E12D2 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,77 @@
{
"pins" : [
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
"revision" : "0625932976b3ae23949f6b816d13bd97f3b40b7c",
"version" : "0.10.0"
}
},
{
"identity" : "jwtdecode.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/auth0/JWTDecode.swift",
"state" : {
"revision" : "7918a343e674c7707e0be120bb4e21d679be014c",
"version" : "3.0.1"
}
},
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
"revision" : "fc45e7b2cfece9dd80b5a45e6469ffe67fe67984",
"version" : "0.14.1"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "f9acfa1a45f4483fe0f2c434a74e6f68f865d12d",
"version" : "0.3.0"
}
},
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "84b30e1af72e0ffe6dfbfe39d53b8173caacf224",
"version" : "0.10.2"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "ad0a6a0dd4d4741263e798f4f5029589c9b5da73",
"version" : "0.4.2"
}
},
{
"identity" : "swiftui-navigation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swiftui-navigation",
"state" : {
"revision" : "47dd574b900ba5ba679f56ea00d4d282fc7305a6",
"version" : "0.7.1"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "4af50b38daf0037cfbab15514a241224c3f62f98",
"version" : "0.8.5"
}
}
],
"version" : 2
}

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8DCC021828D512AC007E12D2"
BuildableName = "Firezone.app"
BlueprintName = "Firezone"
ReferencedContainer = "container:Firezone.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8DCC022E28D512AE007E12D2"
BuildableName = "FirezoneTests.xctest"
BlueprintName = "FirezoneTests"
ReferencedContainer = "container:Firezone.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FirezoneKitTests"
BuildableName = "FirezoneKitTests"
BlueprintName = "FirezoneKitTests"
ReferencedContainer = "container:FirezoneKit">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8DCC021828D512AC007E12D2"
BuildableName = "Firezone.app"
BlueprintName = "Firezone"
ReferencedContainer = "container:Firezone.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8DCC021828D512AC007E12D2"
BuildableName = "Firezone.app"
BlueprintName = "Firezone"
ReferencedContainer = "container:Firezone.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,50 @@
//
// FirezoneApp.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import FirezoneKit
import SwiftUI
@main
struct FirezoneApp: App {
#if os(macOS)
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#endif
#if os(iOS)
@StateObject var model = AppViewModel()
#endif
var body: some Scene {
#if os(iOS)
WindowGroup {
AppView(model: model)
}
#else
WindowGroup("Settings") {
SettingsView(model: appDelegate.settingsViewModel)
}
.handlesExternalEvents(matching: ["settings"])
#endif
}
}
#if os(macOS)
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
let settingsViewModel = SettingsViewModel()
private var menuBar: MenuBar!
func applicationDidFinishLaunching(_: Notification) {
menuBar = MenuBar(settingsViewModel: settingsViewModel)
// SwiftUI will show the first window group, so close it on launch
let window = NSApp.windows[0]
window.close()
}
func applicationWillTerminate(_: Notification) {}
}
#endif

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,63 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1 @@
#include "Developer.xcconfig"

View File

@@ -0,0 +1,2 @@
DEVELOPMENT_TEAM = 0000000000
PRODUCT_BUNDLE_IDENTIFIER = dev.firezone.ios

View File

@@ -0,0 +1,2 @@
DEVELOPMENT_TEAM = 0000000000
PRODUCT_BUNDLE_IDENTIFIER = dev.firezone.macos

View File

@@ -0,0 +1,7 @@
// You Apple developer account's Team ID
DEVELOPMENT_TEAM = <team_id>
// The bundle identifier of the apps.
// Should be an app id created at developer.apple.com
// with Network Extensions capability.
PRODUCT_BUNDLE_IDENTIFIER = <app_id>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="false" userDefinedModelVersionIdentifier="">
<entity name="Item" representedClassName="Item" syncable="YES" codeGenerationType="class">
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<elements>
<element name="Item" positionX="-63" positionY="-18" width="128" height="44"/>
</elements>
</model>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>firezone</string>
<key>CFBundleURLSchemes</key>
<array>
<string>firezone</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

9
swift/apple/FirezoneKit/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@@ -0,0 +1,86 @@
{
"pins" : [
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
"revision" : "882ac01eb7ef9e36d4467eb4b1151e74fcef85ab",
"version" : "0.9.1"
}
},
{
"identity" : "jwtdecode.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/auth0/JWTDecode.swift",
"state" : {
"revision" : "7918a343e674c7707e0be120bb4e21d679be014c",
"version" : "3.0.1"
}
},
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
"revision" : "870133b7b2387df136ad301ec67b2e864b51dda1",
"version" : "0.14.0"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "20b25ca0dd88ebfb9111ec937814ddc5a8880172",
"version" : "0.2.0"
}
},
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "de8ba65649e7ee317b9daf27dd5eebf34bd4be57",
"version" : "0.9.1"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "6bb1034e8a1bfbf46dfb766b6c09b7b17e1cba10",
"version" : "0.2.0"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log",
"state" : {
"revision" : "32e8d724467f8fe623624570367e3d50c5638e46",
"version" : "1.5.2"
}
},
{
"identity" : "swiftui-navigation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swiftui-navigation",
"state" : {
"revision" : "47dd574b900ba5ba679f56ea00d4d282fc7305a6",
"version" : "0.7.1"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "ab8c9f45843694dd16be4297e6d44c0634fd9913",
"version" : "0.8.4"
}
}
],
"version" : 2
}

View File

@@ -0,0 +1,39 @@
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "FirezoneKit",
platforms: [.iOS(.v15), .macOS(.v12)],
products: [
// Products define the executables and libraries a package produces, and make them visible to
// other packages.
.library(name: "FirezoneKit", targets: ["FirezoneKit", "Connlib"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.1.3"),
.package(url: "https://github.com/pointfreeco/swiftui-navigation", from: "0.5.0"),
.package(url: "https://github.com/auth0/JWTDecode.swift", from: "3.0.0"),
],
targets: [
.binaryTarget(
name: "Connlib",
path: "../../../rust/connlib/clients/apple/Connlib.xcframework"
),
.target(
name: "FirezoneKit",
dependencies: [
.product(name: "SwiftUINavigation", package: "swiftui-navigation"),
.product(name: "_SwiftUINavigationState", package: "swiftui-navigation"),
.product(name: "Dependencies", package: "swift-dependencies"),
.product(name: "JWTDecode", package: "JWTDecode.swift"),
]
),
.testTarget(
name: "FirezoneKitTests",
dependencies: ["FirezoneKit"]
),
]
)

View File

@@ -0,0 +1,3 @@
# FirezoneKit
A description of this package.

View File

@@ -0,0 +1,105 @@
//
// AuthClient.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import AuthenticationServices
import Dependencies
import Foundation
import JWTDecode
enum AuthClientError: Error {
case invalidCallbackURL(URL?)
case jwtDecoderFailure(Error)
case sessionFailure(Error)
}
struct AuthClient: Sendable {
var signIn: @Sendable (URL) async throws -> Token
}
extension AuthClient: DependencyKey {
static var liveValue: AuthClient {
let session = WebAuthenticationSession()
return AuthClient(
signIn: { host in
try await session.signIn(host)
}
)
}
}
extension DependencyValues {
var auth: AuthClient {
get { self[AuthClient.self] }
set { self[AuthClient.self] = newValue }
}
}
private final class WebAuthenticationSession: NSObject,
ASWebAuthenticationPresentationContextProviding
{
@MainActor
func signIn(_ host: URL) async throws -> Token {
try await withCheckedThrowingContinuation { continuation in
let callbackURLScheme = "firezone-fd0020211111"
let session = ASWebAuthenticationSession(
url: host.appendingPathComponent("auth")
.appendingQueryItem(URLQueryItem(name: "dest", value: "\(callbackURLScheme)://auth")),
callbackURLScheme: callbackURLScheme
) { callbackURL, error in
if let error {
continuation.resume(throwing: AuthClientError.sessionFailure(error))
return
}
guard let callbackURL else {
continuation.resume(throwing: AuthClientError.invalidCallbackURL(callbackURL))
return
}
guard
let jwt = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?
.queryItems?
.first(where: { $0.name == "client_auth_token" })?
.value
else {
continuation.resume(throwing: AuthClientError.invalidCallbackURL(callbackURL))
return
}
do {
let token = try Token(portalURL: host, tokenString: jwt)
continuation.resume(returning: token)
} catch {
continuation.resume(throwing: AuthClientError.jwtDecoderFailure(error))
}
}
session.presentationContextProvider = self
session.prefersEphemeralWebBrowserSession = true
session.start()
}
}
func presentationAnchor(for _: ASWebAuthenticationSession) -> ASPresentationAnchor {
ASPresentationAnchor()
}
}
extension URL {
func appendingQueryItem(_ queryItem: URLQueryItem) -> URL {
guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else {
return self
}
if components.queryItems == nil {
components.queryItems = []
}
components.queryItems!.append(queryItem)
return components.url ?? self
}
}

View File

@@ -0,0 +1,52 @@
//
// AppView.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import _SwiftUINavigationState
import Combine
import Dependencies
import SwiftUI
import SwiftUINavigation
@MainActor
public final class AppViewModel: ObservableObject {
@Published var welcomeViewModel: WelcomeViewModel?
public init() {
Task {
let tunnel = try await TunnelStore.loadOrCreate()
self.welcomeViewModel = WelcomeViewModel(
appStore: AppStore(
tunnelStore: TunnelStore(
tunnel: tunnel
)
)
)
}
}
}
public struct AppView: View {
@ObservedObject var model: AppViewModel
public init(model: AppViewModel) {
self.model = model
}
@ViewBuilder
public var body: some View {
if let model = model.welcomeViewModel {
WelcomeView(model: model)
} else {
ProgressView()
}
}
}
struct AppView_Previews: PreviewProvider {
static var previews: some View {
AppView(model: AppViewModel())
}
}

View File

@@ -0,0 +1,58 @@
//
// AuthView.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import AuthenticationServices
import Combine
import Dependencies
import JWTDecode
import SwiftUI
import XCTestDynamicOverlay
@MainActor
final class AuthViewModel: ObservableObject {
@Dependency(\.settingsClient) private var settingsClient
@Dependency(\.authStore) private var authStore
var settingsUndefined: () -> Void = unimplemented("\(AuthViewModel.self).settingsUndefined")
private var cancellables = Set<AnyCancellable>()
func logInButtonTapped() async {
guard let portalURL = settingsClient.fetchSettings()?.portalURL else {
settingsUndefined()
return
}
do {
try await authStore.signIn(portalURL: portalURL)
} catch {
dump(error)
}
}
}
struct AuthView: View {
@ObservedObject var model: AuthViewModel
var body: some View {
VStack {
Text("Welcome to Firezone").font(.largeTitle)
Button("Log in") {
Task {
await model.logInButtonTapped()
}
}
.buttonStyle(.borderedProminent)
}
}
}
struct AuthView_Previews: PreviewProvider {
static var previews: some View {
AuthView(model: AuthViewModel())
}
}

View File

@@ -0,0 +1,93 @@
//
// MainView.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Combine
import Dependencies
import NetworkExtension
import OSLog
import SwiftUI
@MainActor
final class MainViewModel: ObservableObject {
private let logger = Logger.make(for: MainViewModel.self)
private var cancellables: Set<AnyCancellable> = []
private let appStore: AppStore
var token: Token? {
appStore.auth.token
}
var status: NEVPNStatus {
appStore.tunnel.status
}
init(appStore: AppStore) {
self.appStore = appStore
appStore.objectWillChange
.sink { [weak self] in self?.objectWillChange.send() }
.store(in: &cancellables)
}
func signOutButtonTapped() {
appStore.auth.signOut()
}
func startTunnel() async {
do {
if let token = token {
try await appStore.tunnel.start(token: token)
}
} catch {
logger.error("Error starting tunnel: \(String(describing: error))")
}
}
func stopTunnel() {
appStore.tunnel.stop()
}
}
struct MainView: View {
@ObservedObject var model: MainViewModel
var body: some View {
VStack(spacing: 56) {
VStack {
Text("Authenticated").font(.title)
Text(model.token?.user ?? "").foregroundColor(.secondary)
}
Button("Sign out") {
model.signOutButtonTapped()
}
}
.toolbar {
ToolbarItem(placement: .principal) {
ConnectionSwitch(
status: model.status,
connect: { await model.startTunnel() },
disconnect: { model.stopTunnel() }
)
}
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView(
model: MainViewModel(
appStore: AppStore(
tunnelStore: TunnelStore(
tunnel: NETunnelProviderManager()
)
)
)
)
}
}

View File

@@ -0,0 +1,119 @@
//
// SettingsView.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Dependencies
import SwiftUI
import XCTestDynamicOverlay
public final class SettingsViewModel: ObservableObject {
@Dependency(\.settingsClient) private var settingsClient
@Published var settings: Settings
public var onSettingsSaved: () -> Void = unimplemented()
public init() {
settings = Settings(portalURL: nil)
if let storedSettings = settingsClient.fetchSettings() {
settings = storedSettings
}
}
func saveButtonTapped() {
settingsClient.saveSettings(settings)
onSettingsSaved()
}
}
public struct SettingsView: View {
@ObservedObject var model: SettingsViewModel
public init(model: SettingsViewModel) {
self.model = model
}
public var body: some View {
#if os(iOS)
ios
#elseif os(macOS)
mac
#else
#error("Unsupported platform")
#endif
}
#if os(iOS)
private var ios: some View {
NavigationView {
form
}
}
#endif
#if os(macOS)
private var mac: some View {
form
}
#endif
private var form: some View {
Form {
Section {
FormTextField(
title: "Portal URL",
placeholder: "http://localhost:4567",
text: Binding(
get: { model.settings.portalURL?.absoluteString ?? "" },
set: { model.settings.portalURL = URL(string: $0) }
)
)
}
}
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Save") {
model.saveButtonTapped()
}
}
}
}
}
struct FormTextField: View {
let title: String
let placeholder: String
let text: Binding<String>
var body: some View {
#if os(iOS)
HStack {
Text(title)
Spacer()
TextField(placeholder, text: text)
.autocorrectionDisabled()
.multilineTextAlignment(.trailing)
.foregroundColor(.secondary)
.frame(maxWidth: .infinity)
.textInputAutocapitalization(.never)
.textContentType(.URL)
.keyboardType(.URL)
}
#else
TextField(title, text: text, prompt: Text(placeholder))
.autocorrectionDisabled()
.multilineTextAlignment(.trailing)
.foregroundColor(.secondary)
#endif
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView(model: SettingsViewModel())
}
}

View File

@@ -0,0 +1,157 @@
//
// WelcomeView.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import _SwiftUINavigationState
import Combine
import Dependencies
import SwiftUI
import SwiftUINavigation
@MainActor
final class WelcomeViewModel: ObservableObject {
@Dependency(\.settingsClient) private var settingsClient
@Dependency(\.mainQueue) private var mainQueue
private var cancellables = Set<AnyCancellable>()
enum Destination {
case settings(SettingsViewModel)
case undefinedSettingsAlert(AlertState<UndefinedSettingsAlertAction>)
}
enum UndefinedSettingsAlertAction {
case confirmDefineSettingsButtonTapped
}
enum State {
case unauthenticated(AuthViewModel)
case authenticated(MainViewModel)
}
@Published var destination: Destination? {
didSet {
bindDestination()
}
}
@Published var state: State? {
didSet {
bindState()
}
}
private let appStore: AppStore
init(appStore: AppStore) {
self.appStore = appStore
appStore.objectWillChange
.receive(on: mainQueue)
.sink { [weak self] in self?.objectWillChange.send() }
.store(in: &cancellables)
defer { bindDestination() }
if settingsClient.fetchSettings()?.portalURL == nil {
destination = .undefinedSettingsAlert(.undefinedSettings)
}
appStore.auth.$token
.receive(on: mainQueue)
.sink(receiveValue: { [weak self] token in
guard let self else {
return
}
if token != nil {
self.state = .authenticated(MainViewModel(appStore: self.appStore))
} else {
self.state = .unauthenticated(AuthViewModel())
}
})
.store(in: &cancellables)
}
func settingsButtonTapped() {
destination = .settings(SettingsViewModel())
}
func handleUndefinedSettingsAlertAction(_ action: UndefinedSettingsAlertAction) {
switch action {
case .confirmDefineSettingsButtonTapped:
destination = .settings(SettingsViewModel())
}
}
private func bindDestination() {
switch destination {
case let .settings(model):
model.onSettingsSaved = { [weak self] in
self?.destination = nil
self?.state = .unauthenticated(AuthViewModel())
}
case .undefinedSettingsAlert, .none:
break
}
}
private func bindState() {
switch state {
case let .unauthenticated(model):
model.settingsUndefined = { [weak self] in
self?.destination = .undefinedSettingsAlert(.undefinedSettings)
}
case .authenticated, .none:
break
}
}
}
struct WelcomeView: View {
@ObservedObject var model: WelcomeViewModel
var body: some View {
NavigationView {
Group {
switch model.state {
case let .unauthenticated(model):
AuthView(model: model)
case let .authenticated(model):
MainView(model: model)
case .none:
ProgressView()
}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
model.settingsButtonTapped()
} label: {
Label("Settings", systemImage: "gear")
}
}
}
}
.sheet(unwrapping: $model.destination, case: /WelcomeViewModel.Destination.settings) { $model in
SettingsView(model: model)
}
.alert(
unwrapping: $model.destination,
case: /WelcomeViewModel.Destination.undefinedSettingsAlert,
action: model.handleUndefinedSettingsAlertAction
)
}
}
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
WelcomeView(
model: WelcomeViewModel(appStore: AppStore(tunnelStore: TunnelStore(tunnel: .init())))
)
}
}

View File

@@ -0,0 +1,19 @@
//
// Logger.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Dependencies
import Foundation
import OSLog
extension Logger {
static func make(for type: (some Any).Type) -> Logger {
make(category: String(describing: type))
}
static func make(category: String) -> Logger {
Logger(subsystem: Bundle.main.bundleIdentifier ?? "dev.firezone.firezone", category: category)
}
}

View File

@@ -0,0 +1,19 @@
//
// Optional+Unwrap.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Foundation
extension Optional {
/// Unwraps `self` or throws error if `none`.
/// - Parameter error: Error to thrown in case of nil value.
/// - Returns: The wrapped optional value.
func unwrap(throwing error: @autoclosure () -> Error) throws -> Wrapped {
guard let self else {
throw error()
}
return self
}
}

View File

@@ -0,0 +1,28 @@
//
// Keychain+Token.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import JWTDecode
extension KeychainStorage {
static let tokenKey = "token"
func tokenString() async throws -> String? {
let jwt = try await load(KeychainStorage.tokenKey).flatMap { data in
String(data: data, encoding: .utf8)
}
guard let jwt else { return nil }
return jwt
}
func save(tokenString: String) async throws {
try await store(KeychainStorage.tokenKey, tokenString.data(using: .utf8)!)
}
func deleteTokenString() async throws {
try await delete(KeychainStorage.tokenKey)
}
}

View File

@@ -0,0 +1,98 @@
//
// Keychain.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Foundation
enum KeychainError: Error {
case securityError(Status)
}
actor Keychain {
private static let account = "Firezone"
func store(key: String, data: Data) throws {
let query = ([
kSecClass: kSecClassGenericPassword,
kSecAttrService: getServiceIdentifier(key),
kSecAttrAccount: Keychain.account,
kSecValueData: data,
] as [CFString: Any]) as CFDictionary
let status = SecItemAdd(query, nil)
if status == Status.duplicateItem {
try update(key: key, data: data)
} else if status != Status.success {
throw securityError(status)
}
}
func update(key: String, data: Data) throws {
let query = ([
kSecClass: kSecClassGenericPassword,
kSecAttrService: getServiceIdentifier(key),
kSecAttrAccount: Keychain.account,
] as [CFString: Any]) as CFDictionary
let updatedData = [kSecValueData: data] as CFDictionary
let status = SecItemUpdate(query, updatedData)
if status != Status.success {
throw securityError(status)
}
}
func load(key: String) throws -> Data? {
let query = ([
kSecClass: kSecClassGenericPassword,
kSecAttrService: getServiceIdentifier(key),
kSecAttrAccount: Keychain.account,
kSecReturnData: kCFBooleanTrue!,
kSecMatchLimit: kSecMatchLimitOne,
] as [CFString: Any]) as CFDictionary
var data: AnyObject?
let status = SecItemCopyMatching(query, &data)
if status == Status.success {
return data as? Data
} else if status == Status.itemNotFound {
return nil
} else {
throw securityError(status)
}
}
func delete(key: String) throws {
let query = ([
kSecClass: kSecClassGenericPassword,
kSecAttrService: getServiceIdentifier(key),
kSecAttrAccount: Keychain.account,
] as [CFString: Any]) as CFDictionary
let status = SecItemDelete(query)
if status != Status.success {
throw securityError(status)
}
}
private func getServiceIdentifier(_ key: String) -> String {
var bundleIdentifier = Bundle.main.bundleIdentifier ?? "dev.firezone.firezone"
if bundleIdentifier.hasSuffix(".network-extension") {
bundleIdentifier.removeLast(".network-extension".count)
}
return bundleIdentifier + "." + key
}
private func securityError(_ status: OSStatus) -> Error {
KeychainError.securityError(Status(rawValue: status)!)
}
}

View File

@@ -0,0 +1,50 @@
//
// KeychainStorage.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Dependencies
import Foundation
struct KeychainStorage: Sendable {
var store: @Sendable (String, Data) async throws -> Void
var load: @Sendable (String) async throws -> Data?
var delete: @Sendable (String) async throws -> Void
}
extension KeychainStorage: DependencyKey {
static var liveValue: KeychainStorage {
let keychain = Keychain()
return KeychainStorage(
store: { try await keychain.store(key: $0, data: $1) },
load: { try await keychain.load(key: $0) },
delete: { try await keychain.delete(key: $0) }
)
}
static var testValue: KeychainStorage {
let storage = LockIsolated([String: Data]())
return KeychainStorage(
store: { key, data in
storage.withValue {
$0[key] = data
}
},
load: { storage.value[$0] },
delete: { key in
storage.withValue {
$0[key] = nil
}
}
)
}
}
extension DependencyValues {
var keychain: KeychainStorage {
get { self[KeychainStorage.self] }
set { self[KeychainStorage.self] = newValue }
}
}

View File

@@ -0,0 +1,431 @@
//
// Status.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
// swiftlint:disable type_body_length
// swiftlint:disable file_length
// swiftlint:disable identifier_name
// Copyright (c) 2014 kishikawa katsumi
// The MIT License (MIT)
// https://github.com/kishikawakatsumi/KeychainAccess
import Foundation
public enum Status: OSStatus, Error {
case success = 0
case unimplemented = -4
case diskFull = -34
case io = -36
case opWr = -49
case param = -50
case wrPerm = -61
case allocate = -108
case userCanceled = -128
case badReq = -909
case internalComponent = -2070
case notAvailable = -25291
case readOnly = -25292
case authFailed = -25293
case noSuchKeychain = -25294
case invalidKeychain = -25295
case duplicateKeychain = -25296
case duplicateCallback = -25297
case invalidCallback = -25298
case duplicateItem = -25299
case itemNotFound = -25300
case bufferTooSmall = -25301
case dataTooLarge = -25302
case noSuchAttr = -25303
case invalidItemRef = -25304
case invalidSearchRef = -25305
case noSuchClass = -25306
case noDefaultKeychain = -25307
case interactionNotAllowed = -25308
case readOnlyAttr = -25309
case wrongSecVersion = -25310
case keySizeNotAllowed = -25311
case noStorageModule = -25312
case noCertificateModule = -25313
case noPolicyModule = -25314
case interactionRequired = -25315
case dataNotAvailable = -25316
case dataNotModifiable = -25317
case createChainFailed = -25318
case invalidPrefsDomain = -25319
case inDarkWake = -25320
case aclNotSimple = -25240
case policyNotFound = -25241
case invalidTrustSetting = -25242
case noAccessForItem = -25243
case invalidOwnerEdit = -25244
case trustNotAvailable = -25245
case unsupportedFormat = -25256
case unknownFormat = -25257
case keyIsSensitive = -25258
case multiplePrivKeys = -25259
case passphraseRequired = -25260
case invalidPasswordRef = -25261
case invalidTrustSettings = -25262
case noTrustSettings = -25263
case pkcs12VerifyFailure = -25264
case invalidCertificate = -26265
case notSigner = -26267
case policyDenied = -26270
case invalidKey = -26274
case decode = -26275
case `internal` = -26276
case unsupportedAlgorithm = -26268
case unsupportedOperation = -26271
case unsupportedPadding = -26273
case itemInvalidKey = -34000
case itemInvalidKeyType = -34001
case itemInvalidValue = -34002
case itemClassMissing = -34003
case itemMatchUnsupported = -34004
case useItemListUnsupported = -34005
case useKeychainUnsupported = -34006
case useKeychainListUnsupported = -34007
case returnDataUnsupported = -34008
case returnAttributesUnsupported = -34009
case returnRefUnsupported = -34010
case returnPersitentRefUnsupported = -34011
case valueRefUnsupported = -34012
case valuePersistentRefUnsupported = -34013
case returnMissingPointer = -34014
case matchLimitUnsupported = -34015
case itemIllegalQuery = -34016
case waitForCallback = -34017
case missingEntitlement = -34018
case upgradePending = -34019
case mpSignatureInvalid = -25327
case otrTooOld = -25328
case otrIDTooNew = -25329
case serviceNotAvailable = -67585
case insufficientClientID = -67586
case deviceReset = -67587
case deviceFailed = -67588
case appleAddAppACLSubject = -67589
case applePublicKeyIncomplete = -67590
case appleSignatureMismatch = -67591
case appleInvalidKeyStartDate = -67592
case appleInvalidKeyEndDate = -67593
case conversionError = -67594
case appleSSLv2Rollback = -67595
case quotaExceeded = -67596
case fileTooBig = -67597
case invalidDatabaseBlob = -67598
case invalidKeyBlob = -67599
case incompatibleDatabaseBlob = -67600
case incompatibleKeyBlob = -67601
case hostNameMismatch = -67602
case unknownCriticalExtensionFlag = -67603
case noBasicConstraints = -67604
case noBasicConstraintsCA = -67605
case invalidAuthorityKeyID = -67606
case invalidSubjectKeyID = -67607
case invalidKeyUsageForPolicy = -67608
case invalidExtendedKeyUsage = -67609
case invalidIDLinkage = -67610
case pathLengthConstraintExceeded = -67611
case invalidRoot = -67612
case crlExpired = -67613
case crlNotValidYet = -67614
case crlNotFound = -67615
case crlServerDown = -67616
case crlBadURI = -67617
case unknownCertExtension = -67618
case unknownCRLExtension = -67619
case crlNotTrusted = -67620
case crlPolicyFailed = -67621
case idpFailure = -67622
case smimeEmailAddressesNotFound = -67623
case smimeBadExtendedKeyUsage = -67624
case smimeBadKeyUsage = -67625
case smimeKeyUsageNotCritical = -67626
case smimeNoEmailAddress = -67627
case smimeSubjAltNameNotCritical = -67628
case sslBadExtendedKeyUsage = -67629
case ocspBadResponse = -67630
case ocspBadRequest = -67631
case ocspUnavailable = -67632
case ocspStatusUnrecognized = -67633
case endOfData = -67634
case incompleteCertRevocationCheck = -67635
case networkFailure = -67636
case ocspNotTrustedToAnchor = -67637
case recordModified = -67638
case ocspSignatureError = -67639
case ocspNoSigner = -67640
case ocspResponderMalformedReq = -67641
case ocspResponderInternalError = -67642
case ocspResponderTryLater = -67643
case ocspResponderSignatureRequired = -67644
case ocspResponderUnauthorized = -67645
case ocspResponseNonceMismatch = -67646
case codeSigningBadCertChainLength = -67647
case codeSigningNoBasicConstraints = -67648
case codeSigningBadPathLengthConstraint = -67649
case codeSigningNoExtendedKeyUsage = -67650
case codeSigningDevelopment = -67651
case resourceSignBadCertChainLength = -67652
case resourceSignBadExtKeyUsage = -67653
case trustSettingDeny = -67654
case invalidSubjectName = -67655
case unknownQualifiedCertStatement = -67656
case mobileMeRequestQueued = -67657
case mobileMeRequestRedirected = -67658
case mobileMeServerError = -67659
case mobileMeServerNotAvailable = -67660
case mobileMeServerAlreadyExists = -67661
case mobileMeServerServiceErr = -67662
case mobileMeRequestAlreadyPending = -67663
case mobileMeNoRequestPending = -67664
case mobileMeCSRVerifyFailure = -67665
case mobileMeFailedConsistencyCheck = -67666
case notInitialized = -67667
case invalidHandleUsage = -67668
case pvcReferentNotFound = -67669
case functionIntegrityFail = -67670
case internalError = -67671
case memoryError = -67672
case invalidData = -67673
case mdsError = -67674
case invalidPointer = -67675
case selfCheckFailed = -67676
case functionFailed = -67677
case moduleManifestVerifyFailed = -67678
case invalidGUID = -67679
case invalidHandle = -67680
case invalidDBList = -67681
case invalidPassthroughID = -67682
case invalidNetworkAddress = -67683
case crlAlreadySigned = -67684
case invalidNumberOfFields = -67685
case verificationFailure = -67686
case unknownTag = -67687
case invalidSignature = -67688
case invalidName = -67689
case invalidCertificateRef = -67690
case invalidCertificateGroup = -67691
case tagNotFound = -67692
case invalidQuery = -67693
case invalidValue = -67694
case callbackFailed = -67695
case aclDeleteFailed = -67696
case aclReplaceFailed = -67697
case aclAddFailed = -67698
case aclChangeFailed = -67699
case invalidAccessCredentials = -67700
case invalidRecord = -67701
case invalidACL = -67702
case invalidSampleValue = -67703
case incompatibleVersion = -67704
case privilegeNotGranted = -67705
case invalidScope = -67706
case pvcAlreadyConfigured = -67707
case invalidPVC = -67708
case emmLoadFailed = -67709
case emmUnloadFailed = -67710
case addinLoadFailed = -67711
case invalidKeyRef = -67712
case invalidKeyHierarchy = -67713
case addinUnloadFailed = -67714
case libraryReferenceNotFound = -67715
case invalidAddinFunctionTable = -67716
case invalidServiceMask = -67717
case moduleNotLoaded = -67718
case invalidSubServiceID = -67719
case attributeNotInContext = -67720
case moduleManagerInitializeFailed = -67721
case moduleManagerNotFound = -67722
case eventNotificationCallbackNotFound = -67723
case inputLengthError = -67724
case outputLengthError = -67725
case privilegeNotSupported = -67726
case deviceError = -67727
case attachHandleBusy = -67728
case notLoggedIn = -67729
case algorithmMismatch = -67730
case keyUsageIncorrect = -67731
case keyBlobTypeIncorrect = -67732
case keyHeaderInconsistent = -67733
case unsupportedKeyFormat = -67734
case unsupportedKeySize = -67735
case invalidKeyUsageMask = -67736
case unsupportedKeyUsageMask = -67737
case invalidKeyAttributeMask = -67738
case unsupportedKeyAttributeMask = -67739
case invalidKeyLabel = -67740
case unsupportedKeyLabel = -67741
case invalidKeyFormat = -67742
case unsupportedVectorOfBuffers = -67743
case invalidInputVector = -67744
case invalidOutputVector = -67745
case invalidContext = -67746
case invalidAlgorithm = -67747
case invalidAttributeKey = -67748
case missingAttributeKey = -67749
case invalidAttributeInitVector = -67750
case missingAttributeInitVector = -67751
case invalidAttributeSalt = -67752
case missingAttributeSalt = -67753
case invalidAttributePadding = -67754
case missingAttributePadding = -67755
case invalidAttributeRandom = -67756
case missingAttributeRandom = -67757
case invalidAttributeSeed = -67758
case missingAttributeSeed = -67759
case invalidAttributePassphrase = -67760
case missingAttributePassphrase = -67761
case invalidAttributeKeyLength = -67762
case missingAttributeKeyLength = -67763
case invalidAttributeBlockSize = -67764
case missingAttributeBlockSize = -67765
case invalidAttributeOutputSize = -67766
case missingAttributeOutputSize = -67767
case invalidAttributeRounds = -67768
case missingAttributeRounds = -67769
case invalidAlgorithmParms = -67770
case missingAlgorithmParms = -67771
case invalidAttributeLabel = -67772
case missingAttributeLabel = -67773
case invalidAttributeKeyType = -67774
case missingAttributeKeyType = -67775
case invalidAttributeMode = -67776
case missingAttributeMode = -67777
case invalidAttributeEffectiveBits = -67778
case missingAttributeEffectiveBits = -67779
case invalidAttributeStartDate = -67780
case missingAttributeStartDate = -67781
case invalidAttributeEndDate = -67782
case missingAttributeEndDate = -67783
case invalidAttributeVersion = -67784
case missingAttributeVersion = -67785
case invalidAttributePrime = -67786
case missingAttributePrime = -67787
case invalidAttributeBase = -67788
case missingAttributeBase = -67789
case invalidAttributeSubprime = -67790
case missingAttributeSubprime = -67791
case invalidAttributeIterationCount = -67792
case missingAttributeIterationCount = -67793
case invalidAttributeDLDBHandle = -67794
case missingAttributeDLDBHandle = -67795
case invalidAttributeAccessCredentials = -67796
case missingAttributeAccessCredentials = -67797
case invalidAttributePublicKeyFormat = -67798
case missingAttributePublicKeyFormat = -67799
case invalidAttributePrivateKeyFormat = -67800
case missingAttributePrivateKeyFormat = -67801
case invalidAttributeSymmetricKeyFormat = -67802
case missingAttributeSymmetricKeyFormat = -67803
case invalidAttributeWrappedKeyFormat = -67804
case missingAttributeWrappedKeyFormat = -67805
case stagedOperationInProgress = -67806
case stagedOperationNotStarted = -67807
case verifyFailed = -67808
case querySizeUnknown = -67809
case blockSizeMismatch = -67810
case publicKeyInconsistent = -67811
case deviceVerifyFailed = -67812
case invalidLoginName = -67813
case alreadyLoggedIn = -67814
case invalidDigestAlgorithm = -67815
case invalidCRLGroup = -67816
case certificateCannotOperate = -67817
case certificateExpired = -67818
case certificateNotValidYet = -67819
case certificateRevoked = -67820
case certificateSuspended = -67821
case insufficientCredentials = -67822
case invalidAction = -67823
case invalidAuthority = -67824
case verifyActionFailed = -67825
case invalidCertAuthority = -67826
case invaldCRLAuthority = -67827
case invalidCRLEncoding = -67828
case invalidCRLType = -67829
case invalidCRL = -67830
case invalidFormType = -67831
case invalidID = -67832
case invalidIdentifier = -67833
case invalidIndex = -67834
case invalidPolicyIdentifiers = -67835
case invalidTimeString = -67836
case invalidReason = -67837
case invalidRequestInputs = -67838
case invalidResponseVector = -67839
case invalidStopOnPolicy = -67840
case invalidTuple = -67841
case multipleValuesUnsupported = -67842
case notTrusted = -67843
case noDefaultAuthority = -67844
case rejectedForm = -67845
case requestLost = -67846
case requestRejected = -67847
case unsupportedAddressType = -67848
case unsupportedService = -67849
case invalidTupleGroup = -67850
case invalidBaseACLs = -67851
case invalidTupleCredendtials = -67852
case invalidEncoding = -67853
case invalidValidityPeriod = -67854
case invalidRequestor = -67855
case requestDescriptor = -67856
case invalidBundleInfo = -67857
case invalidCRLIndex = -67858
case noFieldValues = -67859
case unsupportedFieldFormat = -67860
case unsupportedIndexInfo = -67861
case unsupportedLocality = -67862
case unsupportedNumAttributes = -67863
case unsupportedNumIndexes = -67864
case unsupportedNumRecordTypes = -67865
case fieldSpecifiedMultiple = -67866
case incompatibleFieldFormat = -67867
case invalidParsingModule = -67868
case databaseLocked = -67869
case datastoreIsOpen = -67870
case missingValue = -67871
case unsupportedQueryLimits = -67872
case unsupportedNumSelectionPreds = -67873
case unsupportedOperator = -67874
case invalidDBLocation = -67875
case invalidAccessRequest = -67876
case invalidIndexInfo = -67877
case invalidNewOwner = -67878
case invalidModifyMode = -67879
case missingRequiredExtension = -67880
case extendedKeyUsageNotCritical = -67881
case timestampMissing = -67882
case timestampInvalid = -67883
case timestampNotTrusted = -67884
case timestampServiceNotAvailable = -67885
case timestampBadAlg = -67886
case timestampBadRequest = -67887
case timestampBadDataFormat = -67888
case timestampTimeNotAvailable = -67889
case timestampUnacceptedPolicy = -67890
case timestampUnacceptedExtension = -67891
case timestampAddInfoNotAvailable = -67892
case timestampSystemFailure = -67893
case signingTimeMissing = -67894
case timestampRejection = -67895
case timestampWaiting = -67896
case timestampRevocationWarning = -67897
case timestampRevocationNotification = -67898
case unexpectedError = -99999
}
extension Status {
static func == (lhs: OSStatus, rhs: Status) -> Bool {
lhs == rhs.rawValue
}
static func != (lhs: OSStatus, rhs: Status) -> Bool {
lhs != rhs.rawValue
}
}

View File

@@ -0,0 +1,11 @@
//
// FirezoneError.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Foundation
enum FirezoneError: Error {
case missingPortalURL
}

View File

@@ -0,0 +1,59 @@
//
// Token.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Foundation
import JWTDecode
struct Token {
// The user associated with this token parsed from the jwt sub claim.
var user: String? { jwt["sub"].string }
// The VPN session duration parsed from the jwt exp claim.
var expiresAt: Date? { jwt["exp"].date }
// A convenience property to check if the token has expired.
var expired: Bool { jwt.expired }
// The base64 encoded jwt string.
var string: String { jwt.string }
// The portal URL
let portalURL: URL
// The decoded jwt.
private let jwt: JWT
init(portalURL: URL, tokenString: String) throws {
self.portalURL = portalURL
self.jwt = try decode(jwt: tokenString)
}
}
extension Token: Hashable {
static func == (lhs: Token, rhs: Token) -> Bool {
lhs.string == rhs.string
}
func hash(into hasher: inout Hasher) {
string.hash(into: &hasher)
}
}
#if DEBUG
extension Token {
static let expired =
try! Token(
portalURL: URL(string: "http://localhost:4568")!,
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0QGZpcmV6b25lLmRldiIsImV4cCI6MTY2NzE0MDU1NX0.YLLQyfX6AlHQb90AMRrBTbvBuRxBzVYe0YwohfcD0r7KdBmR5Y-AcP0eYVC2DK-MSSJVjzs2j7SMPCvwJRc1z0LX_U4PkHjL5HPUIb3_rE1MIP8Hn8Ng5mk6SaTj6EJm3qTmm44bPiy21kntcqp-b9CSFqwc1IQHVHXnbcqcv4sVit2sTXJSNvNRRtO8ZTsC007T9skYBGVfCI-kSFyxQe9CoPQxYzFF8KKtCqmmT-t5g0et78IcwToOYeCxc0zOe14OQFadDZabmvJ_xfvC4iRKPfbOyQfNQqIQ_xh3iaGry2iSD4yMALKvgA7Ij4Ixz0GEnyqEfvOeCRA1UoFcLg"
)
static let valid =
try! Token(
portalURL: URL(string: "http://localhost:4568")!,
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0QGZpcmV6b25lLmRldiIsImV4cCI6MTY2OTk0MDU1NX0.cLkLdIUBF5FH9e32yBPcfOXun11iYhtbxsVdqlZt3U5J-CYfBNikg5jbG6N7h2BYCBjScnmpu7G249la-lahO7IZWM3qil4NNyhKMZxzA_3cgC3362MBX-7xmlpxjR61b_yE1wOfGjjm_xOKIUTfkkfyTGxvQkXecdbOgpZ7WV4PkG7QD-JHgaQvIQCMbQuC5-d225z6rC43itiRxkq5mRet1d6N5VPxq1tdOD4N7mMs9I1NjJpFGcmJw8r8tlik8r7oxlJpmZN6aw4wcHV3gMd_Dmm-ZYW8M6aVvOpNIabi4hvB0Snqx-w4SqHy1stjfm-vHiGfusucX4G4igk4Qw"
)
}
#endif

View File

@@ -0,0 +1,11 @@
//
// Settings.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Foundation
struct Settings: Codable, Hashable {
var portalURL: URL?
}

View File

@@ -0,0 +1,44 @@
//
// SettingsClient.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Dependencies
import Foundation
struct SettingsClient {
var fetchSettings: () -> Settings?
var saveSettings: (Settings?) -> Void
}
extension SettingsClient: DependencyKey {
static let liveValue = SettingsClient(
fetchSettings: {
guard let data = UserDefaults.standard.data(forKey: "settings") else {
return nil
}
return try? JSONDecoder().decode(Settings.self, from: data)
},
saveSettings: { settings in
let data = try? JSONEncoder().encode(settings)
UserDefaults.standard.set(data, forKey: "settings")
}
)
static var testValue: SettingsClient {
let settings = LockIsolated(Settings?.none)
return SettingsClient(
fetchSettings: { settings.value },
saveSettings: { settings.setValue($0) }
)
}
}
extension DependencyValues {
var settingsClient: SettingsClient {
get { self[SettingsClient.self] }
set { self[SettingsClient.self] = newValue }
}
}

View File

@@ -0,0 +1,60 @@
//
// AppStore.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Combine
import Dependencies
import OSLog
@MainActor
final class AppStore: ObservableObject {
private let logger = Logger.make(for: AppStore.self)
@Dependency(\.authStore) var auth
@Dependency(\.mainQueue) var mainQueue
let tunnel: TunnelStore
private var cancellables: Set<AnyCancellable> = []
init(tunnelStore: TunnelStore) {
tunnel = tunnelStore
Publishers.Merge(
auth.objectWillChange,
tunnel.objectWillChange
)
.receive(on: mainQueue)
.sink { [weak self] in
self?.objectWillChange.send()
}
.store(in: &cancellables)
auth.$token
.receive(on: mainQueue)
.sink { [weak self] token in
Task { [weak self] in
await self?.handleTokenChanged(token)
}
}
.store(in: &cancellables)
}
private func handleTokenChanged(_ token: Token?) async {
if let token = token {
do {
try await tunnel.start(token: token)
} catch {
logger.error("Error starting tunnel: \(String(describing: error))")
}
} else {
tunnel.stop()
}
}
private func signOutAndStopTunnel() {
tunnel.stop()
auth.signOut()
}
}

View File

@@ -0,0 +1,92 @@
//
// AuthStore.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Combine
import Dependencies
import Foundation
import OSLog
extension AuthStore: DependencyKey {
static var liveValue: AuthStore = .shared
}
extension DependencyValues {
var authStore: AuthStore {
get { self[AuthStore.self] }
set { self[AuthStore.self] = newValue }
}
}
@MainActor
final class AuthStore: ObservableObject {
private let logger = Logger.make(for: AuthStore.self)
static let shared = AuthStore()
@Dependency(\.keychain) private var keychain
@Dependency(\.auth) private var auth
@Dependency(\.settingsClient) private var settingsClient
private var cancellables = Set<AnyCancellable>()
@Published private(set) var token: Token?
private init() {
Task {
self.token = await {
guard let portalURL = settingsClient.fetchSettings()?.portalURL else {
logger.debug("No portal URL found in settings")
return nil
}
guard let tokenString = try? await keychain.tokenString() else {
logger.debug("Token string not found in keychain")
return nil
}
guard let token = try? Token(portalURL: portalURL, tokenString: tokenString) else {
logger.debug("Token string recovered from keychain is invalid")
return nil
}
logger.debug("Token recovered from keychain.")
return token
}()
}
$token.dropFirst()
.sink { [weak self] token in
Task { [weak self] in
if let token {
try? await self?.keychain.save(tokenString: token.string)
self?.logger.debug("token saved on keychain.")
} else {
try? await self?.keychain.deleteTokenString()
self?.logger.debug("token deleted from keychain.")
}
}
}
.store(in: &cancellables)
}
func signIn(portalURL: URL) async throws {
logger.trace("\(#function)")
let token = try await auth.signIn(portalURL)
self.token = token
}
func signIn() async throws {
logger.trace("\(#function)")
let portalURL = try settingsClient.fetchSettings().flatMap(\.portalURL)
.unwrap(throwing: FirezoneError.missingPortalURL)
try await signIn(portalURL: portalURL)
}
func signOut() {
logger.trace("\(#function)")
token = nil
}
}

View File

@@ -0,0 +1,148 @@
//
// TunnelStore.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import Combine
import Foundation
import NetworkExtension
import OSLog
// TODO: Can this file be removed since we're managing the tunnel in connlib?
@MainActor
final class TunnelStore: ObservableObject {
private static let logger = Logger.make(for: TunnelStore.self)
var tunnel: NETunnelProviderManager {
didSet { setupTunnelObservers() }
}
@Published private(set) var status: NEVPNStatus = .invalid {
didSet { TunnelStore.logger.info("status changed: \(self.status.description)") }
}
@Published private(set) var isEnabled = false {
didSet { TunnelStore.logger.info("isEnabled changed: \(self.isEnabled.description)") }
}
private var tunnelObservingTasks: [Task<Void, Never>] = []
init(tunnel: NETunnelProviderManager) {
self.tunnel = tunnel
tunnel.isEnabled = true
setupTunnelObservers()
}
static func loadOrCreate() async throws -> NETunnelProviderManager {
logger.trace("\(#function)")
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
if let tunnel = managers.first {
return tunnel
}
let tunnel = makeManager()
try await tunnel.saveToPreferences()
try await tunnel.loadFromPreferences()
return tunnel
}
func start(token: Token) async throws {
TunnelStore.logger.trace("\(#function)")
// make sure we have latest preferences before starting
try await tunnel.loadFromPreferences()
tunnel.protocolConfiguration = Self.makeProtocolConfiguration(token: token)
tunnel.isEnabled = true
try await tunnel.saveToPreferences()
let session = tunnel.connection as! NETunnelProviderSession
try session.startTunnel()
}
func stop() {
TunnelStore.logger.trace("\(#function)")
let session = tunnel.connection as! NETunnelProviderSession
session.stopTunnel()
}
private static func makeManager() -> NETunnelProviderManager {
logger.trace("\(#function)")
let manager = NETunnelProviderManager()
manager.localizedDescription = "Firezone"
let proto = makeProtocolConfiguration()
manager.protocolConfiguration = proto
manager.isEnabled = true
return manager
}
private static func makeProtocolConfiguration(token: Token? = nil) -> NETunnelProviderProtocol {
let proto = NETunnelProviderProtocol()
proto.providerBundleIdentifier = Bundle.main.bundleIdentifier.map {
"\($0).network-extension"
}
if let token = token {
proto.providerConfiguration = [
"portalURL": token.portalURL.absoluteString,
"token": token.string
]
}
proto.serverAddress = "Firezone addresses"
return proto
}
private func setupTunnelObservers() {
TunnelStore.logger.trace("\(#function)")
tunnelObservingTasks.forEach { $0.cancel() }
tunnelObservingTasks.removeAll()
tunnelObservingTasks.append(
Task {
for await notification in NotificationCenter.default.notifications(
named: .NEVPNStatusDidChange,
object: nil
) {
guard let session = notification.object as? NETunnelProviderSession,
let tunnelProvider = session.manager as? NETunnelProviderManager
else {
return
}
self.status = tunnelProvider.connection.status
}
}
)
}
func removeProfile() async throws {
TunnelStore.logger.trace("\(#function)")
try await tunnel.removeFromPreferences()
}
}
// MARK: - Extensions
/// Make NEVPNStatus convertible to a string
extension NEVPNStatus: CustomStringConvertible {
public var description: String {
switch self {
case .disconnected: return "Disconnected"
case .invalid: return "Invalid"
case .connected: return "Connected"
case .connecting: return "Connecting"
case .disconnecting: return "Disconnecting"
case .reasserting: return "Reconnecting"
@unknown default: return "Unknown"
}
}
}

View File

@@ -0,0 +1,18 @@
//
// Alerts.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import _SwiftUINavigationState
extension AlertState where Action == WelcomeViewModel.UndefinedSettingsAlertAction {
static let undefinedSettings = AlertState(
title: TextState("No settings found."),
message: TextState("To sign in, you first need to configure portal settings."),
dismissButton: .default(
TextState("Define settings"),
action: .send(.confirmDefineSettingsButtonTapped)
)
)
}

View File

@@ -0,0 +1,55 @@
//
// ConnectionSwitch.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import NetworkExtension
import SwiftUI
struct ConnectionSwitch: View {
let status: NEVPNStatus
var connect: () async -> Void
var disconnect: () async -> Void
@State private var isInFlight = false
var body: some View {
HStack {
ZStack {
Toggle(
"", isOn: .init(
get: { status == .connected },
set: { isOn in
Task {
isInFlight = true
defer { isInFlight = false }
if isOn {
await connect()
} else {
await disconnect()
}
}
}
)
)
.labelsHidden()
.toggleStyle(.switch)
.opacity(isInFlight ? 0 : 1)
if isInFlight {
ProgressView()
}
}
Text(status.description).frame(maxWidth: .infinity, alignment: .leading)
}
}
}
struct ConnectionSwitch_Previews: PreviewProvider {
static var previews: some View {
ConnectionSwitch(status: .connected, connect: {}, disconnect: {})
}
}

View File

@@ -0,0 +1,202 @@
//
// MenuBar.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
// swiftlint:disable function_parameter_count
#if os(macOS)
import Combine
import Dependencies
import OSLog
import SwiftUI
@MainActor
public final class MenuBar {
let logger = Logger.make(for: MenuBar.self)
@Dependency(\.mainQueue) private var mainQueue
private var appStore: AppStore? {
didSet {
setupObservers()
}
}
private var cancellables: Set<AnyCancellable> = []
private var statusItem: NSStatusItem
let settingsViewModel: SettingsViewModel
public init(settingsViewModel: SettingsViewModel) {
self.settingsViewModel = settingsViewModel
settingsViewModel.onSettingsSaved = {
// TODO: close settings window and sign in
}
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem.button {
button.image = NSImage(
// TODO: Replace with AppIcon when it exists
systemSymbolName: "circle",
accessibilityDescription: "Firezone icon"
)
}
createMenu()
Task {
let tunnel = try await TunnelStore.loadOrCreate()
self.appStore = AppStore(tunnelStore: TunnelStore(tunnel: tunnel))
}
}
private func setupObservers() {
appStore?.auth.$token
.receive(on: mainQueue)
.sink { [weak self] token in
if let token {
self?.showLoggedIn(token.user)
} else {
self?.showLoggedOut()
}
}
.store(in: &cancellables)
appStore?.tunnel.$status
.receive(on: mainQueue)
.sink { [weak self] status in
if status == .connected {
self?.connectionMenuItem.title = "Disconnect"
} else {
self?.connectionMenuItem.title = "Connect"
}
}
.store(in: &cancellables)
}
private lazy var menu = NSMenu()
private lazy var connectionMenuItem = createMenuItem(
menu,
title: "Connect",
action: #selector(connectButtonTapped),
target: self
)
private lazy var loginMenuItem = createMenuItem(
menu,
title: "Login",
action: #selector(loginButtonTapped),
target: self
)
private lazy var logoutMenuItem = createMenuItem(
menu,
title: "Logout",
action: #selector(logoutButtonTapped),
isHidden: true,
target: self
)
private lazy var settingsMenuItem = createMenuItem(
menu,
title: "Settings",
action: #selector(settingsButtonTapped),
target: self
)
private lazy var quitMenuItem = createMenuItem(
menu,
title: "Quit",
action: #selector(NSApplication.terminate(_:)),
key: "q",
target: nil
)
private func createMenu() {
menu.addItem(connectionMenuItem)
menu.addItem(loginMenuItem)
menu.addItem(logoutMenuItem)
menu.addItem(NSMenuItem.separator())
menu.addItem(settingsMenuItem)
menu.addItem(quitMenuItem)
statusItem.menu = menu
}
private func createMenuItem(
_: NSMenu,
title: String,
action: Selector,
isHidden: Bool = false,
key: String = "",
target: AnyObject?
) -> NSMenuItem {
let item = NSMenuItem(title: title, action: action, keyEquivalent: key)
item.isHidden = isHidden
item.target = target
return item
}
private func showLoggedIn(_ user: String?) {
if let user {
loginMenuItem.title = "Logged in as \(user)"
} else {
loginMenuItem.title = "Logged in"
}
loginMenuItem.target = nil
logoutMenuItem.isHidden = false
connectionMenuItem.isHidden = false
}
private func showLoggedOut() {
loginMenuItem.title = "Login"
loginMenuItem.target = self
logoutMenuItem.isHidden = true
connectionMenuItem.isHidden = true
}
@objc private func connectButtonTapped() {
if appStore?.tunnel.status == .connected {
appStore?.tunnel.stop()
} else {
Task {
if let token = appStore?.auth.token {
do {
try await appStore?.tunnel.start(token: token)
} catch {
logger.error("error connecting to tunnel: \(String(describing: error))")
}
}
}
}
}
@objc private func loginButtonTapped() {
Task {
do {
try await appStore?.auth.signIn()
} catch FirezoneError.missingPortalURL {
openSettingsWindow()
} catch {
logger.error("Error signing in: \(String(describing: error))")
}
}
}
@objc private func logoutButtonTapped() {
appStore?.auth.signOut()
}
@objc private func settingsButtonTapped() {
openSettingsWindow()
}
private func openSettingsWindow() {
NSWorkspace.shared.open(URL(string: "firezone://settings")!)
}
}
#endif

View File

@@ -0,0 +1,11 @@
//
// AppStoreTests.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import XCTest
@testable import FirezoneKit
final class AppStoreTests: XCTestCase {}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.application-groups</key>
<array/>
</dict>
</plist>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,73 @@
//
// PacketTunnelProvider.swift
// (c) 2023 Firezone, Inc.
// LICENSE: Apache-2.0
//
import connlib
import Dependencies
import NetworkExtension
import os
enum PacketTunnelProviderError: String, Error {
case savedProtocolConfigurationIsInvalid
case couldNotSetNetworkSettings
}
class PacketTunnelProvider: NEPacketTunnelProvider {
static let logger = Logger(subsystem: "dev.firezone.firezone", category: "packet-tunnel")
private lazy var adapter = Adapter(with: self)
override func startTunnel(
options _: [String: NSObject]? = nil,
completionHandler: @escaping (Error?) -> Void
) {
guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol else {
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
return
}
let providerConfiguration = tunnelProviderProtocol.providerConfiguration
guard let portalURL = providerConfiguration?["portalURL"] as? String else {
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
return
}
guard let token = providerConfiguration?["token"] as? String else {
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
return
}
Self.logger.log("portalURL = \(portalURL, privacy: .public)")
Self.logger.log("token = \(token, privacy: .public)")
do {
// Once connlib is updated to take in portalURL and token, this call
// should become adapter.start(portalURL: portalURL, token: token)
try adapter.start { error in
if let error {
Self.logger.error("Error in adapter.start: \(error)")
}
completionHandler(error)
}
} catch {
completionHandler(error)
}
}
override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) {
adapter.stop { error in
if let error {
Self.logger.error("Error in adapter.stop: \(error)")
}
completionHandler()
}
#if os(macOS)
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
// Remove it when they finally fix this upstream and the fix has been rolled out to
// sufficient quantities of users.
exit(0)
#endif
}
}

201
swift/apple/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2023 Firezone, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

17
swift/apple/Makefile Normal file
View File

@@ -0,0 +1,17 @@
# Creates a macOS debug build
PLATFORM=macOS
ARCH=$(shell uname -m)
build-macos:
echo "Building debug build for ${PLATFORM}, ${ARCH}"
cd ../../rust/connlib/clients/apple && rm -rf ./Connlib.xcframework && ./build-rust.sh && ./build-xcframework-dev.sh
@xcodebuild build -scheme Firezone -sdk macosx -destination 'platform=${PLATFORM},arch=${ARCH}'
clean:
@xcodebuild clean -scheme Firezone -sdk macosx -destination 'platform=${PLATFORM},arch=${ARCH}'
cd ../../rust/connlib/clients/apple && rm -rf ./Connlib.xcframework
.PHONY: format
format:
@swiftformat .

View File

@@ -0,0 +1 @@
ruby 3.1.0

View File

@@ -0,0 +1,4 @@
source "https://rubygems.org"
gem 'sinatra'
gem 'thin'

View File

@@ -0,0 +1,32 @@
GEM
remote: https://rubygems.org/
specs:
daemons (1.4.1)
eventmachine (1.2.7)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
rack (2.2.4)
rack-protection (3.0.4)
rack
ruby2_keywords (0.0.5)
sinatra (3.0.4)
mustermann (~> 3.0)
rack (~> 2.2, >= 2.2.4)
rack-protection (= 3.0.4)
tilt (~> 2.0)
thin (1.8.1)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
rack (>= 1, < 3)
tilt (2.0.11)
PLATFORMS
arm64-darwin-21
arm64-darwin-22
DEPENDENCIES
sinatra
thin
BUNDLED WITH
2.3.3

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1 @@
eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjFMN3k3RUM1T3VSZUNNNnIzX2l0MXNJbjNqeTdiZ2JPSVB3Z0xoejV0SGsifQ.eyJpc3MiOiJodHRwczovL2ZpcmV6b25lLmxvY2FsIiwic3ViIjoidGVzdEBmaXJlem9uZS5kZXYiLCJjbGllbnRfaWQiOiJmaXJlem9uZSIsImV4cCI6MTY3MjgzNzU0NCwiaWF0IjoxNjY4MTMzOTQ0fQ.NvvGWvrMvshKp5MYycDWXa8gQ41Ptrr_nIKzfPWzci8fxwmQYJ5hL1vQpdmECtR5NeGv7qTavi6yq19Kqmwrn27numDXaET2b2xypGbFOm1TJmcbZ4Rxy_-FfAeer-7YNhW_p83a0N7UoPORpxVs8hp76sKe_klfmoM830frrLzeqz0VYxBZXhPiTAlqiG39cY74yk-drxLY4xeRBAXh_TdewrkRkPpTpsrXFz60fF5P8AaRnUKlDSRq89ZIC-zo2ysJsXIZLrJpfcNgkscohZZfXfCLIFaiGvZseW0XHWfq-V5HOXVf09-57GHdmCr-AAJ7sqpnPrSBvg7EDBvylg

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env ruby
require 'sinatra'
require 'erb'
set :bind, '0.0.0.0'
set :port, 4568
get '/auth' do
dest = params['dest']
ERB.new("<h1>Auth page</h1><a href=\"/redirect?dest=#{dest}\">Proceed</a>").result(binding)
end
get '/redirect' do
dest = params['dest']
client_auth_token = File.read('./data/client_auth_token').strip
redirect "#{dest}?client_auth_token=#{client_auth_token}"
end

View File

@@ -8,7 +8,14 @@ Clone this repo:
```bash
git clone https://github.com/firezone/firezone
cd swift
```
Build Connlib:
```bash
cd rust/connlib/clients/apple
PLATFORM_NAME=macosx ./build-rust.sh # For macOS
PLATFORM_NAME=iphoneos ./build-rust.sh # For iOS
./build-xcframework-dev.sh
```
Rename and populate developer team ID file:
@@ -23,3 +30,5 @@ Open project in Xcode:
```bash
open Firezone.xcodeproj
```
Build the Firezone target