Files
teleport/mobile/Verify/Verify/DeepLink.swift
T
Maja Cieślak 813776fd7a Add Xcode project for Teleport Verify; add gomobile as tool dep (#67750)
* Generate Xcode project for Verify

* Set Swift version to 6; remove macOS and Vision as destinations

* Handle `teleport://` links and add enrollment screen

* Avoid building macOS-specific stuff on iOS

This is needed for gomobile to build our stuff from lib/mobile from the
next commit.

* Add `lib/mobile/verify/enroll` compiled with gomobile

* `lib/mobile`: Temporarily use private Device Trust service

The public gRPC service exists on the demo branch but not on this
branch. In order to make it possible to compile
`lib/mobile/verify/enroll`, temporarily use the private service which
has similar RPCs.

* Run `make go-mod-tidy-all`

* Make Swift app connect to public DT service on "Request Now" tap

* mobile: Add sheet for enroll request status

* Revert go.mod changes

This reverts commit b9af7a7614 and
2c00ecd7fa.

* Add `build.assets/tools/gomobile`

* Move gomobile back to main go.mod

This reverts commits 9b2a6c4ea3 and
a83c2959fa.
2026-06-16 17:29:26 +00:00

104 lines
3.4 KiB
Swift

// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import Foundation
import os
private let logger = Logger(
subsystem: Bundle.main.bundleIdentifier ?? "com.gravitational.verify",
category: "deep_link"
)
func parseDeepLink(_ url: URL) -> DeepLinkParseResult {
let path = url.path(percentEncoded: false)
switch path {
case "/enroll_mobile_device":
// Make sure to first switch on the path and only then attempt to parse out individual fields
// out of the URL. This way this function always returns the most important error first,
// which is the error about the unsupported path.
return parseDeepURL(url) { deepURL, parts in
guard
let enrollPairingToken = getQueryParam(
parts,
"enroll_pairing_token"
)
else {
return .failure(.missingPart("enroll pairing token"))
}
return .success(
.enrollMobileDevice(
EnrollMobileDeviceDeepURL(
url: deepURL,
enrollPairingToken: enrollPairingToken
)
)
)
}
default:
logger.warning("Unsupported path: \(path, privacy: .public)")
return .failure(.unsupportedPath)
}
}
typealias DeepLinkParseResult = Result<ParsedDeepLink, DeepLinkParseError>
enum ParsedDeepLink {
case enrollMobileDevice(EnrollMobileDeviceDeepURL)
}
enum DeepLinkParseError: Error {
case unsupportedPath
case urlComponentsFailed
case missingPart(String)
}
struct DeepURL {
/// hostname is just the name of the host without the port.
var hostname: String
/// port of the host.
var port: Int?
}
struct EnrollMobileDeviceDeepURL {
var url: DeepURL
/// user_token query params from the deep link.
var enrollPairingToken: String
}
/// parseDeepURL could return Result<(DeepURL, URLComponents), DeepLinkParseError> instead, but
/// there's no convienient way to unwrap it, that's why it accepts a trailing closure instead.
func parseDeepURL(
_ url: URL,
toParsedDeepLink: (_ deepURL: DeepURL, _ parts: URLComponents) ->
DeepLinkParseResult
) -> DeepLinkParseResult {
guard let parts = URLComponents(string: url.absoluteString) else {
return .failure(.urlComponentsFailed)
}
guard let hostname = parts.host, hostname != "" else {
return .failure(.missingPart("hostname"))
}
let port = parts.port
return toParsedDeepLink(DeepURL(hostname: hostname, port: port), parts)
}
func getQueryParam(_ parts: URLComponents, _ param: String) -> String? {
parts.queryItems?
.first(where: { $0.name == param && $0.value != nil && $0.value != "" }
)?.value
}