summaryrefslogtreecommitdiff
path: root/octosentry/SecurityEventListView.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-17 17:04:15 -0500
committerChristian Cleberg <[email protected]>2026-07-17 17:04:15 -0500
commit8e6d29f1806cc569b48d913564c7d80a1c2f2355 (patch)
tree60ae62636b45c1a6368f4d0b3c119df74b6ba029 /octosentry/SecurityEventListView.swift
parent2f72d3d0736a6fac306d4e5d9406cb33b08bc2a0 (diff)
downloadoctosentry-8e6d29f1806cc569b48d913564c7d80a1c2f2355.tar.gz
octosentry-8e6d29f1806cc569b48d913564c7d80a1c2f2355.tar.bz2
octosentry-8e6d29f1806cc569b48d913564c7d80a1c2f2355.zip
Replace env-var PAT with GitHub device authorization flow + Keychain0.4.0
Closes #6, #7 (milestone 0.4.0). - GitHubDeviceAuthClient implements the OAuth 2.0 device authorization grant (device code request + poll for token) against GitHub's OAuth App endpoints. Verified against the real endpoints directly. - KeychainTokenStore stores the resulting token in the app's own Keychain item (not synced to iCloud Keychain), no shared entitlement needed since nothing else reads it. - AuthStore drives the sign-in state machine (signedOut / awaitingAuthorization / signedIn) and a new SignInView replaces the old "missing token" error state with an actual sign-in UI. - SecurityEventStore now reads the token from Keychain instead of the GITHUB_TOKEN environment variable, which is fully retired. - Scope requested is security_events, the narrowest available for classic OAuth Apps (no read-only variant exists at this level, unlike fine-grained PATs). Private-repo Dependabot alerts may need broader repo scope — to be confirmed with real-world testing.
Diffstat (limited to 'octosentry/SecurityEventListView.swift')
-rw-r--r--octosentry/SecurityEventListView.swift35
1 files changed, 25 insertions, 10 deletions
diff --git a/octosentry/SecurityEventListView.swift b/octosentry/SecurityEventListView.swift
index a5dba3e..298aa2f 100644
--- a/octosentry/SecurityEventListView.swift
+++ b/octosentry/SecurityEventListView.swift
@@ -8,20 +8,24 @@ import SwiftUI
struct SecurityEventListView: View {
var store: SecurityEventStore
+ var authStore: AuthStore
@State private var showingRepoManager = false
var body: some View {
VStack(alignment: .leading, spacing: 0) {
header
Divider()
- if showingRepoManager {
- RepoManagerView(store: store)
+ if !authStore.isSignedIn {
+ SignInView(authStore: authStore)
+ } else if showingRepoManager {
+ RepoManagerView(store: store, authStore: authStore)
} else {
content
}
}
.frame(width: 380, height: 420)
- .task {
+ .task(id: authStore.isSignedIn) {
+ guard authStore.isSignedIn else { return }
await store.refresh()
store.startPolling()
}
@@ -39,7 +43,7 @@ struct SecurityEventListView: View {
Spacer()
- if !showingRepoManager {
+ if authStore.isSignedIn && !showingRepoManager {
Picker("Minimum severity", selection: Binding(
get: { store.minimumSeverity },
set: { newValue in Task { await store.setMinimumSeverity(newValue) } }
@@ -61,12 +65,14 @@ struct SecurityEventListView: View {
.disabled(store.isLoading)
}
- Button {
- showingRepoManager.toggle()
- } label: {
- Image(systemName: showingRepoManager ? "xmark.circle" : "gearshape")
+ if authStore.isSignedIn {
+ Button {
+ showingRepoManager.toggle()
+ } label: {
+ Image(systemName: showingRepoManager ? "xmark.circle" : "gearshape")
+ }
+ .buttonStyle(.plain)
}
- .buttonStyle(.plain)
Button("Quit") {
NSApplication.shared.terminate(nil)
@@ -121,6 +127,7 @@ struct SecurityEventListView: View {
private struct RepoManagerView: View {
var store: SecurityEventStore
+ var authStore: AuthStore
@State private var newRepoText = ""
var body: some View {
@@ -167,6 +174,14 @@ private struct RepoManagerView: View {
}
Spacer()
+
+ Divider()
+
+ Button("Sign Out") {
+ authStore.signOut()
+ }
+ .buttonStyle(.plain)
+ .foregroundStyle(.red)
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -234,5 +249,5 @@ private struct StatusView: View {
}
#Preview {
- SecurityEventListView(store: SecurityEventStore())
+ SecurityEventListView(store: SecurityEventStore(), authStore: AuthStore())
}