Repository navigation
Expand file tree
/
Copy pathgit-push-gate.swift
More file actions
65 lines (56 loc) · 2.31 KB
/
Copy pathgit-push-gate.swift
File metadata and controls
65 lines (56 loc) · 2.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// Copyright 2026 the git-push-gate authors
//
// 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.
// git-push-gate — require an explicit Touch ID confirmation before a git push proceeds.
//
// Usage: git-push-gate "push to some-org/some-repo: 3 commits to main (a919d7d..4f2b1c9)"
// Exit: 0 = authenticated, 1 = cancelled/failed, 2 = no authentication mechanism available.
//
// The argument is shown verbatim in the system dialog, so the caller can name exactly what is
// being authorised. macOS renders it as "<binary name> is trying to <reason>", so phrase the
// reason as a lowercase verb phrase.
import Foundation
import LocalAuthentication
let reason =
CommandLine.arguments.count > 1 && !CommandLine.arguments[1].isEmpty
? CommandLine.arguments[1]
: "authorize this git push"
func fail(_ message: String, _ code: Int32) -> Never {
FileHandle.standardError.write(Data("git-push-gate: \(message)\n".utf8))
exit(code)
}
let context = LAContext()
context.localizedCancelTitle = "Cancel push"
// Prefer biometrics; fall back to password so a clamshell/no-sensor session is recoverable
// rather than a lockout. Both paths still require a human at the keyboard.
var policy = LAPolicy.deviceOwnerAuthenticationWithBiometrics
var probe: NSError?
if !context.canEvaluatePolicy(policy, error: &probe) {
policy = .deviceOwnerAuthentication
if !context.canEvaluatePolicy(policy, error: &probe) {
fail("no authentication available: \(probe?.localizedDescription ?? "unknown error")", 2)
}
}
let done = DispatchSemaphore(value: 0)
var authorized = false
var authError: Error?
context.evaluatePolicy(policy, localizedReason: reason) { success, error in
authorized = success
authError = error
done.signal()
}
done.wait()
guard authorized else {
fail(authError?.localizedDescription ?? "authentication cancelled", 1)
}
exit(0)