Skip to content

Commit f6a061d

Browse files
authored
Merge pull request #424 from MadAppGang/featue/log-tokens
Log tokens for audit records
2 parents 2d2a00b + b68fc6b commit f6a061d

18 files changed

+114
-36
lines changed

model/server_settings.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type ServerSettings struct {
2727
KeyStorage FileStorageSettings `yaml:"keyStorage" json:"key_storage"`
2828
Config FileStorageSettings `yaml:"-" json:"config"`
2929
Logger LoggerSettings `yaml:"logger" json:"logger"`
30+
Audit AuditSettings `yaml:"audit" json:"audit"`
3031
AdminPanel AdminPanelSettings `yaml:"adminPanel" json:"admin_panel"`
3132
LoginWebApp FileStorageSettings `yaml:"loginWebApp" json:"login_web_app"`
3233
EmailTemplates FileStorageSettings `yaml:"emailTemplates" json:"email_templates"`
@@ -368,6 +369,7 @@ type LoggerSettings struct {
368369
// Deprecated: User HTTPDetailing on module level.
369370
DumpRequest bool `yaml:"dumpRequest" json:"dumpRequest"`
370371
Format string `yaml:"format" json:"format"`
372+
MaxBodySize int `yaml:"maxBodySize" json:"maxBodySize"`
371373
LogSensitiveData bool `yaml:"logSensitiveData" json:"logSensitiveData"`
372374
Common LoggerParams `yaml:"common" json:"common"`
373375
API LoggerParams `yaml:"api" json:"api"`
@@ -388,6 +390,18 @@ func HTTPLogDetailing(dumpRequest bool, logType HTTPDetailing) HTTPDetailing {
388390
return logType
389391
}
390392

393+
type TokenRecording string
394+
395+
const (
396+
TokenRecordingNone TokenRecording = "none"
397+
TokenRecordingObfuscated TokenRecording = "obfuscated"
398+
TokenRecordingFull TokenRecording = "full"
399+
)
400+
401+
type AuditSettings struct {
402+
TokenRecording TokenRecording `yaml:"tokenRecording" json:"tokenRecording"`
403+
}
404+
391405
type AdminPanelSettings struct {
392406
Enabled bool `json:"enabled" yaml:"enabled"`
393407
}

model/server_settings_validation.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package model
22

33
import (
4+
"errors"
45
"fmt"
56
"net/url"
67
"os"
@@ -146,7 +147,7 @@ func (sss *SessionStorageSettings) Validate() []error {
146147
result := []error{}
147148

148149
if len(sss.Type) == 0 {
149-
result = append(result, fmt.Errorf("Empty session storage type"))
150+
result = append(result, errors.New("empty session storage type"))
150151
}
151152
if sss.SessionDuration.Duration == 0 {
152153
result = append(result, fmt.Errorf("%s. Session duration is 0 seconds", subject))
@@ -226,7 +227,7 @@ func (sss *SMSServiceSettings) Validate() []error {
226227
subject := "SMSServiceSettings"
227228
result := []error{}
228229
if len(sss.Type) == 0 {
229-
return []error{fmt.Errorf("Empty SMS service type")}
230+
return []error{errors.New("empty SMS service type")}
230231
}
231232

232233
switch sss.Type {

web/admin/router.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func NewRouter(settings RouterSettings) (model.Router, error) {
5757
ar.middleware = buildMiddleware(
5858
settings.LoggerSettings.DumpRequest,
5959
settings.LoggerSettings.Format,
60+
settings.LoggerSettings.MaxBodySize,
6061
settings.LoggerSettings.Admin,
6162
settings.LoggerSettings.LogSensitiveData,
6263
settings.Cors)
@@ -70,6 +71,7 @@ func NewRouter(settings RouterSettings) (model.Router, error) {
7071
func buildMiddleware(
7172
dumpRequest bool,
7273
format string,
74+
maxBodySize int,
7375
logParams model.LoggerParams,
7476
logSensitiveData bool,
7577
corsHandler *cors.Cors,
@@ -79,6 +81,7 @@ func buildMiddleware(
7981
lm := middleware.NegroniHTTPLogger(
8082
logging.ComponentAdmin,
8183
format,
84+
maxBodySize,
8285
logParams,
8386
model.HTTPLogDetailing(dumpRequest, logParams.HTTPDetailing),
8487
!logSensitiveData,

web/api/2fa.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,9 @@ func (ar *Router) FinalizeTFA() http.HandlerFunc {
306306
}
307307
}
308308

309-
ar.journal(JournalOperationLoginWith2FA,
310-
user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes.Scopes())
309+
ar.audit(AuditOperationLoginWith2FA,
310+
user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes.Scopes(),
311+
result.AccessToken, result.RefreshToken)
311312

312313
ar.server.Storages().User.UpdateLoginMetadata(user.ID)
313314
ar.ServeJSON(w, locale, http.StatusOK, result)

web/api/federated_login.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,9 @@ func (ar *Router) FederatedLoginComplete() http.HandlerFunc {
212212
authResult.CallbackUrl = fsess.CallbackUrl
213213
authResult.Scopes = fsess.Scopes
214214

215-
ar.journal(JournalOperationFederatedLogin,
216-
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes())
215+
ar.audit(AuditOperationFederatedLogin,
216+
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes(),
217+
authResult.AccessToken, authResult.RefreshToken)
217218

218219
ar.ServeJSON(w, locale, http.StatusOK, authResult)
219220
}

web/api/federated_oidc_login.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,9 @@ func (ar *Router) OIDCLoginComplete(useSession bool) http.HandlerFunc {
249249
authResult.Scopes = resultScopes.Scopes()
250250
authResult.ProviderData = *providerData
251251

252-
ar.journal(JournalOperationOIDCLogin,
253-
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes())
252+
ar.audit(AuditOperationOIDCLogin,
253+
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes(),
254+
authResult.AccessToken, authResult.RefreshToken)
254255

255256
ar.ServeJSON(w, locale, http.StatusOK, authResult)
256257
}

web/api/impersonate_as.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ func (ar *Router) ImpersonateAs() http.HandlerFunc {
7777
// do not allow refresh for impersonated user
7878
authResult.RefreshToken = ""
7979

80-
ar.journal(JournalOperationImpersonatedAs,
81-
userID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes())
80+
ar.audit(AuditOperationImpersonatedAs,
81+
userID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes(),
82+
authResult.AccessToken, authResult.RefreshToken)
8283

8384
ar.ServeJSON(w, locale, http.StatusOK, authResult)
8485
}

web/api/journal.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,36 @@ package api
22

33
import (
44
"github.com/madappgang/identifo/v2/logging"
5+
"github.com/madappgang/identifo/v2/model"
56
)
67

7-
type JournalOperation string
8+
type AuditOperation string
89

910
const (
10-
JournalOperationLoginWithPassword JournalOperation = "login_with_password"
11-
JournalOperationLoginWithPhone JournalOperation = "login_with_phone"
12-
JournalOperationLoginWith2FA JournalOperation = "login_with_2fa"
13-
JournalOperationRefreshToken JournalOperation = "refresh_token"
14-
JournalOperationOIDCLogin JournalOperation = "oidc_login"
15-
JournalOperationFederatedLogin JournalOperation = "federated_login"
16-
JournalOperationRegistration JournalOperation = "registration"
17-
JournalOperationLogout JournalOperation = "logout"
18-
JournalOperationImpersonatedAs JournalOperation = "impersonated_as"
11+
AuditOperationLoginWithPassword AuditOperation = "login_with_password"
12+
AuditOperationLoginWithPhone AuditOperation = "login_with_phone"
13+
AuditOperationLoginWith2FA AuditOperation = "login_with_2fa"
14+
AuditOperationRefreshToken AuditOperation = "refresh_token"
15+
AuditOperationOIDCLogin AuditOperation = "oidc_login"
16+
AuditOperationFederatedLogin AuditOperation = "federated_login"
17+
AuditOperationRegistration AuditOperation = "registration"
18+
AuditOperationLogout AuditOperation = "logout"
19+
AuditOperationImpersonatedAs AuditOperation = "impersonated_as"
1920
)
2021

21-
func (ar *Router) journal(
22-
op JournalOperation,
22+
func (ar *Router) audit(
23+
op AuditOperation,
2324
userID, appID, device, accessRole string,
2425
scopes []string,
26+
accessToken, refreshToken string,
2527
) {
2628
iss := ar.server.Services().Token.Issuer()
2729

30+
auditSettings := ar.server.Settings().Audit
31+
32+
accessToken = maskToken(accessToken, auditSettings.TokenRecording)
33+
refreshToken = maskToken(refreshToken, auditSettings.TokenRecording)
34+
2835
// TODO: Create an interface for the audit log
2936
// Implement it for logging to stdout, a database, or a remote service
3037
ar.logger.Info("audit_record",
@@ -35,5 +42,24 @@ func (ar *Router) journal(
3542
"issuer", iss,
3643
"accessRole", accessRole,
3744
"scopes", scopes,
45+
"accessToken", accessToken,
46+
"refreshToken", refreshToken,
3847
)
3948
}
49+
50+
func maskToken(token string, tokenRecording model.TokenRecording) string {
51+
switch tokenRecording {
52+
case model.TokenRecordingNone:
53+
return "<redacted>"
54+
case model.TokenRecordingObfuscated:
55+
if len(token) < 32 {
56+
return "<short>"
57+
}
58+
59+
return token[:6] + "..." + token[len(token)-6:]
60+
case model.TokenRecordingFull:
61+
return token
62+
default:
63+
return "<redacted>"
64+
}
65+
}

web/api/login.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ func (ar *Router) LoginWithPassword() http.HandlerFunc {
189189
return
190190
}
191191

192-
ar.journal(JournalOperationLoginWithPassword,
193-
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes())
192+
ar.audit(AuditOperationLoginWithPassword,
193+
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes(),
194+
authResult.AccessToken, authResult.RefreshToken)
194195

195196
ar.ServeJSON(w, locale, http.StatusOK, authResult)
196197
}

web/api/logout.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ func (ar *Router) Logout() http.HandlerFunc {
6464
}
6565
}
6666

67-
ar.journal(JournalOperationLogout,
68-
accessToken.Subject(), accessToken.Audience(), r.UserAgent(), "", nil)
67+
ar.audit(AuditOperationLogout,
68+
accessToken.Subject(), accessToken.Audience(), r.UserAgent(), "", nil,
69+
accessTokenString, d.RefreshToken)
6970

7071
ar.ServeJSON(w, locale, http.StatusOK, result)
7172
}

0 commit comments

Comments
 (0)