-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchecker.go
More file actions
157 lines (134 loc) · 4.09 KB
/
checker.go
File metadata and controls
157 lines (134 loc) · 4.09 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright 2017-2026 Allow2 Pty Ltd. All rights reserved.
// Use of this source code is governed by the Allow2 API and SDK Licence.
package allow2service
import (
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
)
// permissionChecker checks child permissions via the Allow2 Service API.
// Results are cached per user for a configurable TTL.
type permissionChecker struct {
httpClient HTTPClient
cache Cache
serviceHost string
cacheTTL int
}
// Check checks permissions for a user's linked child account.
// Pass an empty string for timezone to use the server default.
func (p *permissionChecker) Check(accessToken, userID string, activities []ActivityInput, timezone string) (*CheckResult, error) {
// Build normalized activity list for API and cache key
normalized := normalizeActivities(activities)
// Check cache first
cacheKey := buildCacheKey(userID, normalized)
if cached, ok := p.cache.Get(cacheKey); ok {
var data map[string]interface{}
if err := json.Unmarshal([]byte(cached), &data); err == nil {
return CheckResultFromAPIResponse(data), nil
}
p.cache.Delete(cacheKey)
}
// Build payload
payload := map[string]interface{}{
"access_token": accessToken,
"activities": normalized,
"log": true,
}
if timezone != "" {
payload["tz"] = timezone
}
response, err := p.httpClient.Post(
p.serviceHost+"/serviceapi/check",
payload,
nil,
)
if err != nil {
return nil, &ApiError{
Allow2Error: Allow2Error{Message: fmt.Sprintf("Permission check request failed: %v", err)},
}
}
// 401/403 = unpaired
if response.IsUnauthorized() {
return nil, &UnpairedError{
Allow2Error: Allow2Error{Message: "Service account is no longer linked. Re-pairing required."},
UserID: userID,
}
}
if !response.IsSuccess() {
body := response.JSON()
return nil, &ApiError{
Allow2Error: Allow2Error{Message: fmt.Sprintf("Permission check failed: HTTP %d", response.StatusCode)},
HTTPStatusCode: response.StatusCode,
ResponseBody: body,
}
}
data := response.JSON()
if data == nil {
return nil, &ApiError{
Allow2Error: Allow2Error{Message: "Permission check returned invalid JSON"},
HTTPStatusCode: response.StatusCode,
}
}
result := CheckResultFromAPIResponse(data)
// Cache the raw response
if rawJSON, err := json.Marshal(data); err == nil {
p.cache.Set(cacheKey, string(rawJSON), p.cacheTTL)
}
return result, nil
}
// IsAllowed is a convenience method: checks if all specified activities are allowed.
func (p *permissionChecker) IsAllowed(accessToken, userID string, activityIDs []int) (bool, error) {
inputs := make([]ActivityInput, len(activityIDs))
for i, id := range activityIDs {
inputs[i] = ActivityInput{ID: id, Log: true}
}
result, err := p.Check(accessToken, userID, inputs, "")
if err != nil {
return false, err
}
for _, activity := range result.Activities {
if !activity.Allowed {
return false, nil
}
}
return true, nil
}
// InvalidateCache invalidates the cached check result for a user.
func (p *permissionChecker) InvalidateCache(userID string, activities []ActivityInput) {
normalized := normalizeActivities(activities)
cacheKey := buildCacheKey(userID, normalized)
p.cache.Delete(cacheKey)
}
// normalizeActivities converts ActivityInput slice to the standard API format.
func normalizeActivities(activities []ActivityInput) []map[string]interface{} {
result := make([]map[string]interface{}, len(activities))
for i, a := range activities {
result[i] = map[string]interface{}{
"id": a.ID,
"log": a.Log,
}
}
return result
}
// buildCacheKey creates a deterministic cache key for a user + activities combination.
func buildCacheKey(userID string, activities []map[string]interface{}) string {
ids := make([]int, 0, len(activities))
for _, a := range activities {
if id, ok := a["id"]; ok {
switch v := id.(type) {
case int:
ids = append(ids, v)
case float64:
ids = append(ids, int(v))
}
}
}
sort.Ints(ids)
parts := make([]string, len(ids))
for i, id := range ids {
parts[i] = strconv.Itoa(id)
}
return "allow2_check_" + userID + "_" + strings.Join(parts, "_")
}