diff --git a/.DS_Store b/.DS_Store
index b352f12..c94ca09 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/client/.DS_Store b/client/.DS_Store
index b7e1b5a..fb9bb09 100644
Binary files a/client/.DS_Store and b/client/.DS_Store differ
diff --git a/client/app.go b/client/app.go
index 35e05ca..29aa498 100644
--- a/client/app.go
+++ b/client/app.go
@@ -124,9 +124,10 @@ type App struct {
lastSyncTime time.Time
isStreamingSc bool
streamScStop chan struct{}
- statusMsg string
- expectingLogin bool
- backendOnline bool
+ statusMsg string
+ expectingLogin bool
+ needsClearPortalStorage bool
+ backendOnline bool
dashboard StudentDashboardSnapshot
wifiEnforce bool
acceptedWifi map[string]bool
@@ -209,6 +210,9 @@ func (a *App) startup(ctx context.Context) {
a.loadSession()
a.loadStats()
+ // Request Location Access (macOS)
+ winapi.RequestLocationAccess()
+
// Register blocker callbacks
blocker.Instance.OnBlocked = func(procName string, title string) {
go a.reportBlockedApp(procName, title)
@@ -534,7 +538,8 @@ func (a *App) NavigateToLogin() {
func (a *App) Logout() {
a.mu.Lock()
a.student = nil
- a.expectingLogin = false
+ a.expectingLogin = true
+ a.needsClearPortalStorage = true
a.mu.Unlock()
_ = os.Remove(a.sessionPath)
@@ -552,7 +557,7 @@ func (a *App) Logout() {
a.stopScreenshotStream()
guard.SuppressFor(5 * time.Second)
- runtime.WindowReloadApp(a.ctx)
+ runtime.WindowExecJS(a.ctx, "window.location.href = 'https://portal.rikkei.edu.vn/dangnhap'")
}
// GetStats trả về Wifi, thời gian online/offline và trạng thái giám sát
@@ -622,9 +627,23 @@ func (a *App) authStorageScanner() {
a.mu.Lock()
expecting := a.expectingLogin
+ needsClear := a.needsClearPortalStorage
loggedIn := a.student != nil
a.mu.Unlock()
+ if needsClear && expecting {
+ runtime.WindowExecJS(a.ctx, `
+ try {
+ localStorage.removeItem("student");
+ localStorage.removeItem("token");
+ } catch(e) {}
+ `)
+ a.mu.Lock()
+ a.needsClearPortalStorage = false
+ a.mu.Unlock()
+ continue
+ }
+
if loggedIn || !expecting {
continue
}
@@ -661,8 +680,16 @@ func (a *App) runMonitorTick() {
wifi := winapi.GetWifiConnection()
a.mu.Lock()
- a.wifiSSID = wifi.SSID
- a.wifiBSSID = wifi.BSSID
+ if strings.Contains(strings.ToLower(wifi.SSID), "redacted") {
+ a.wifiSSID = "Chưa cấp quyền Vị Trí (macOS)"
+ } else {
+ a.wifiSSID = wifi.SSID
+ }
+ if strings.Contains(strings.ToLower(wifi.BSSID), "redacted") {
+ a.wifiBSSID = "Chưa cấp quyền Vị Trí (macOS)"
+ } else {
+ a.wifiBSSID = wifi.BSSID
+ }
a.mu.Unlock()
backendOnline := a.pingBackend()
@@ -732,8 +759,16 @@ func (a *App) refreshNetworkStatus() {
wifi := winapi.GetWifiConnection()
backendOnline := a.pingBackend()
a.mu.Lock()
- a.wifiSSID = wifi.SSID
- a.wifiBSSID = wifi.BSSID
+ if strings.Contains(strings.ToLower(wifi.SSID), "redacted") {
+ a.wifiSSID = "Chưa cấp quyền Vị Trí (macOS)"
+ } else {
+ a.wifiSSID = wifi.SSID
+ }
+ if strings.Contains(strings.ToLower(wifi.BSSID), "redacted") {
+ a.wifiBSSID = "Chưa cấp quyền Vị Trí (macOS)"
+ } else {
+ a.wifiBSSID = wifi.BSSID
+ }
a.backendOnline = backendOnline
a.mu.Unlock()
if backendOnline {
@@ -923,6 +958,10 @@ func (a *App) isWifiAllowed(ssid, bssid string) bool {
if !enforce || len(allowed) == 0 {
return true
}
+ // Bypass Wi-Fi check if it's redacted by macOS privacy controls
+ if strings.Contains(strings.ToLower(ssid), "redacted") || strings.Contains(strings.ToLower(bssid), "redacted") {
+ return true
+ }
bKey := winapi.BSSIDKey(bssid)
if bKey == "" || len(bKey) != 12 {
return false
diff --git a/client/build/.DS_Store b/client/build/.DS_Store
index d680716..7b16691 100644
Binary files a/client/build/.DS_Store and b/client/build/.DS_Store differ
diff --git a/client/build/darwin/Info.dev.plist b/client/build/darwin/Info.dev.plist
index 14121ef..8496abb 100644
--- a/client/build/darwin/Info.dev.plist
+++ b/client/build/darwin/Info.dev.plist
@@ -64,5 +64,7 @@
NSAllowsLocalNetworking
+ NSLocationWhenInUseUsageDescription
+ Ứng dụng cần quyền vị trí để xác thực mạng Wi-Fi phòng thi.
diff --git a/client/build/darwin/Info.plist b/client/build/darwin/Info.plist
index d17a747..a4698d6 100644
--- a/client/build/darwin/Info.plist
+++ b/client/build/darwin/Info.plist
@@ -59,5 +59,7 @@
{{end}}
{{end}}
+ NSLocationWhenInUseUsageDescription
+ Ứng dụng cần quyền vị trí để xác thực mạng Wi-Fi phòng thi.
diff --git a/client/internal/winapi/wifi_darwin.go b/client/internal/winapi/wifi_darwin.go
index 1f5a3af..21cea90 100644
--- a/client/internal/winapi/wifi_darwin.go
+++ b/client/internal/winapi/wifi_darwin.go
@@ -2,45 +2,52 @@
package winapi
+/*
+#cgo LDFLAGS: -framework CoreWLAN -framework CoreLocation -framework Foundation
+#include
+
+typedef struct {
+ char* ssid;
+ char* bssid;
+} CWifiInfo;
+
+void RequestLocationPermission();
+CWifiInfo GetCurrentWifiInfo();
+*/
+import "C"
import (
- "bytes"
- "os/exec"
- "strings"
+ "unsafe"
)
-// GetWifiConnection đọc SSID và BSSID từ ipconfig (macOS)
-func GetWifiConnection() WifiConnection {
- // Find Wi-Fi interface dynamically
- wifiInterface := "en0"
- cmdPort := exec.Command("networksetup", "-listallhardwareports")
- if outPort, err := cmdPort.Output(); err == nil {
- lines := strings.Split(string(outPort), "\n")
- for i := 0; i < len(lines)-1; i++ {
- if strings.Contains(lines[i], "Hardware Port: Wi-Fi") {
- next := strings.TrimSpace(lines[i+1])
- if strings.HasPrefix(next, "Device:") {
- wifiInterface = strings.TrimSpace(strings.TrimPrefix(next, "Device:"))
- break
- }
- }
- }
- }
-
- cmd := exec.Command("ipconfig", "getsummary", wifiInterface)
- var out bytes.Buffer
- cmd.Stdout = &out
- if err := cmd.Run(); err != nil {
- return WifiConnection{}
- }
-
- var conn WifiConnection
- for _, line := range strings.Split(out.String(), "\n") {
- trimmed := strings.TrimSpace(line)
- if strings.HasPrefix(trimmed, "SSID :") {
- conn.SSID = strings.TrimSpace(strings.TrimPrefix(trimmed, "SSID :"))
- } else if strings.HasPrefix(trimmed, "BSSID :") {
- conn.BSSID = normalizeMAC(strings.TrimSpace(strings.TrimPrefix(trimmed, "BSSID :")))
- }
- }
- return conn
+// RequestLocationAccess requests Location permission on macOS
+func RequestLocationAccess() {
+ C.RequestLocationPermission()
+}
+
+// GetWifiConnection đọc SSID và BSSID từ CoreWLAN (macOS)
+func GetWifiConnection() WifiConnection {
+ info := C.GetCurrentWifiInfo()
+ defer func() {
+ if info.ssid != nil {
+ C.free(unsafe.Pointer(info.ssid))
+ }
+ if info.bssid != nil {
+ C.free(unsafe.Pointer(info.bssid))
+ }
+ }()
+
+ ssid := ""
+ if info.ssid != nil {
+ ssid = C.GoString(info.ssid)
+ }
+
+ bssid := ""
+ if info.bssid != nil {
+ bssid = C.GoString(info.bssid)
+ }
+
+ return WifiConnection{
+ SSID: ssid,
+ BSSID: normalizeMAC(bssid),
+ }
}
diff --git a/client/internal/winapi/wifi_darwin.m b/client/internal/winapi/wifi_darwin.m
new file mode 100644
index 0000000..35e8e03
--- /dev/null
+++ b/client/internal/winapi/wifi_darwin.m
@@ -0,0 +1,45 @@
+#import
+#import
+#import
+
+static CLLocationManager *locationManager = nil;
+
+void RequestLocationPermission() {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (locationManager == nil) {
+ locationManager = [[CLLocationManager alloc] init];
+ }
+ if (@available(macOS 10.15, *)) {
+ [locationManager requestWhenInUseAuthorization];
+ }
+ });
+}
+
+typedef struct {
+ char* ssid;
+ char* bssid;
+} CWifiInfo;
+
+CWifiInfo GetCurrentWifiInfo() {
+ CWifiInfo info;
+ info.ssid = NULL;
+ info.bssid = NULL;
+
+ @autoreleasepool {
+ CWWiFiClient *client = [CWWiFiClient sharedWiFiClient];
+ if (client != nil) {
+ CWInterface *interface = [client interface];
+ if (interface != nil) {
+ NSString *ssidStr = [interface ssid];
+ NSString *bssidStr = [interface bssid];
+ if (ssidStr != nil) {
+ info.ssid = strdup([ssidStr UTF8String]);
+ }
+ if (bssidStr != nil) {
+ info.bssid = strdup([bssidStr UTF8String]);
+ }
+ }
+ }
+ }
+ return info;
+}
diff --git a/client/internal/winapi/wifi_other.go b/client/internal/winapi/wifi_other.go
index 550643c..f823f62 100644
--- a/client/internal/winapi/wifi_other.go
+++ b/client/internal/winapi/wifi_other.go
@@ -6,3 +6,6 @@ package winapi
func GetWifiConnection() WifiConnection {
return WifiConnection{}
}
+
+// RequestLocationAccess requests Location permission (stub for other systems)
+func RequestLocationAccess() {}
diff --git a/client/internal/winapi/wifi_windows.go b/client/internal/winapi/wifi_windows.go
index a7e6bf0..4400976 100644
--- a/client/internal/winapi/wifi_windows.go
+++ b/client/internal/winapi/wifi_windows.go
@@ -38,3 +38,6 @@ func GetWifiConnection() WifiConnection {
}
return conn
}
+
+// RequestLocationAccess requests Location permission (stub for Windows)
+func RequestLocationAccess() {}