diff --git a/pkg/jmap/jmap_api_blob.go b/pkg/jmap/jmap_api_blob.go index 415a3c704..2bb317f12 100644 --- a/pkg/jmap/jmap_api_blob.go +++ b/pkg/jmap/jmap_api_blob.go @@ -26,18 +26,21 @@ func (j *Client) GetBlob(accountId string, session *Session, ctx context.Context }, "0"), ) if err != nil { - return BlobResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (BlobResponse, Error) { var response BlobGetResponse err = retrieveResponseMatchParameters(body, CommandBlobGet, "0", &response) if err != nil { - return BlobResponse{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(response.List) != 1 { - return BlobResponse{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Msgf("%T.List has %v entries instead of 1", response, len(response.List)) + return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } get := response.List[0] return BlobResponse{Blob: &get, State: response.State, SessionState: body.SessionState}, nil @@ -104,32 +107,38 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont invocation(CommandBlobGet, getHash, "1"), ) if err != nil { - return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UploadedBlob, Error) { var uploadResponse BlobUploadResponse err = retrieveResponseMatchParameters(body, CommandBlobUpload, "0", &uploadResponse) if err != nil { - return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var getResponse BlobGetResponse err = retrieveResponseMatchParameters(body, CommandBlobGet, "1", &getResponse) if err != nil { - return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(uploadResponse.Created) != 1 { - return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Msgf("%T.Created has %v entries instead of 1", uploadResponse, len(uploadResponse.Created)) + return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } upload, ok := uploadResponse.Created["0"] if !ok { - return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Msgf("%T.Created has no item '0'", uploadResponse) + return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(getResponse.List) != 1 { - return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Msgf("%T.List has %v entries instead of 1", getResponse, len(getResponse.List)) + return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } get := getResponse.List[0] diff --git a/pkg/jmap/jmap_api_email.go b/pkg/jmap/jmap_api_email.go index b97c9f46c..6eb3476bd 100644 --- a/pkg/jmap/jmap_api_email.go +++ b/pkg/jmap/jmap_api_email.go @@ -42,13 +42,15 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte cmd, err := request(invocation(CommandEmailGet, get, "0")) if err != nil { - return Emails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return Emails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Emails, Error) { var response EmailGetResponse err = retrieveResponseMatchParameters(body, CommandEmailGet, "0", &response) if err != nil { - return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return Emails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } return Emails{Emails: response.List, State: response.State, SessionState: body.SessionState}, nil }) @@ -88,19 +90,22 @@ func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Co invocation(CommandEmailGet, get, "1"), ) if err != nil { - return Emails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return Emails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Emails, Error) { var queryResponse EmailQueryResponse err = retrieveResponseMatchParameters(body, CommandEmailQuery, "0", &queryResponse) if err != nil { - return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return Emails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var getResponse EmailGetResponse err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &getResponse) if err != nil { - return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return Emails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } return Emails{ @@ -161,26 +166,30 @@ func (j *Client) GetEmailsInMailboxSince(accountId string, session *Session, ctx invocation(CommandEmailGet, getUpdated, "2"), ) if err != nil { - return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) { var mailboxResponse MailboxChangesResponse err = retrieveResponseMatchParameters(body, CommandMailboxChanges, "0", &mailboxResponse) if err != nil { - return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var createdResponse EmailGetResponse err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &createdResponse) if err != nil { - return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var updatedResponse EmailGetResponse err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &updatedResponse) if err != nil { - return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } return EmailsSince{ @@ -232,26 +241,29 @@ func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context. invocation(CommandEmailGet, getUpdated, "2"), ) if err != nil { - return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) { var changesResponse EmailChangesResponse err = retrieveResponseMatchParameters(body, CommandEmailChanges, "0", &changesResponse) if err != nil { - return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var createdResponse EmailGetResponse err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &createdResponse) if err != nil { - return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var updatedResponse EmailGetResponse err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &updatedResponse) if err != nil { - return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } return EmailsSince{ @@ -311,20 +323,23 @@ func (j *Client) QueryEmailSnippets(accountId string, filter EmailFilterElement, ) if err != nil { - return EmailSnippetQueryResult{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return EmailSnippetQueryResult{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailSnippetQueryResult, Error) { var queryResponse EmailQueryResponse err = retrieveResponseMatchParameters(body, CommandEmailQuery, "0", &queryResponse) if err != nil { - return EmailSnippetQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return EmailSnippetQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var snippetResponse SearchSnippetGetResponse err = retrieveResponseMatchParameters(body, CommandSearchSnippetGet, "1", &snippetResponse) if err != nil { - return EmailSnippetQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return EmailSnippetQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } return EmailSnippetQueryResult{ @@ -401,26 +416,27 @@ func (j *Client) QueryEmails(accountId string, filter EmailFilterElement, sessio ) if err != nil { - return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailQueryResult, Error) { var queryResponse EmailQueryResponse err = retrieveResponseMatchParameters(body, CommandEmailQuery, "0", &queryResponse) if err != nil { - return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var snippetResponse SearchSnippetGetResponse err = retrieveResponseMatchParameters(body, CommandSearchSnippetGet, "1", &snippetResponse) if err != nil { - return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var emailsResponse EmailGetResponse err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &emailsResponse) if err != nil { - return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } snippetsById := map[string][]SearchSnippet{} @@ -496,32 +512,37 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con invocation(CommandBlobGet, getHash, "1"), ) if err != nil { - return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UploadedEmail, Error) { var uploadResponse BlobUploadResponse err = retrieveResponseMatchParameters(body, CommandBlobUpload, "0", &uploadResponse) if err != nil { - return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var getResponse BlobGetResponse err = retrieveResponseMatchParameters(body, CommandBlobGet, "1", &getResponse) if err != nil { - return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(uploadResponse.Created) != 1 { - return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Msgf("%T.Created has %v elements instead of 1", uploadResponse, len(uploadResponse.Created)) + return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } upload, ok := uploadResponse.Created["0"] if !ok { - return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Msgf("%T.Created has no element '0'", uploadResponse) + return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(getResponse.List) != 1 { - return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Msgf("%T.List has %v elements instead of 1", getResponse, len(getResponse.List)) + return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } get := getResponse.List[0] @@ -554,14 +575,16 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, session *Sessi }, "0"), ) if err != nil { - return CreatedEmail{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return CreatedEmail{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (CreatedEmail, Error) { var setResponse EmailSetResponse err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse) if err != nil { - return CreatedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return CreatedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(setResponse.NotCreated) > 0 { @@ -571,12 +594,14 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, session *Sessi setErr, notok := setResponse.NotCreated["c"] if notok { + logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr) return CreatedEmail{}, setErrorError(setErr, EmailType) } created, ok := setResponse.Created["c"] if !ok { err = fmt.Errorf("failed to find %s in %s response", string(EmailType), string(CommandEmailSet)) + logger.Error().Err(err) return CreatedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } @@ -612,14 +637,16 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, }, "0"), ) if err != nil { - return UpdatedEmails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return UpdatedEmails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UpdatedEmails, Error) { var setResponse EmailSetResponse err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse) if err != nil { - return UpdatedEmails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return UpdatedEmails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(setResponse.NotUpdated) != len(updates) { // error occured @@ -648,14 +675,16 @@ func (j *Client) DeleteEmails(accountId string, destroy []string, session *Sessi }, "0"), ) if err != nil { - return DeletedEmails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return DeletedEmails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (DeletedEmails, Error) { var setResponse EmailSetResponse err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse) if err != nil { - return DeletedEmails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return DeletedEmails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(setResponse.NotDestroyed) != len(destroy) { // error occured @@ -724,14 +753,16 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string invocation(CommandEmailSubmissionGet, get, "1"), ) if err != nil { - return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (SubmittedEmail, Error) { var submissionResponse EmailSubmissionSetResponse err = retrieveResponseMatchParameters(body, CommandEmailSubmissionSet, "0", &submissionResponse) if err != nil { - return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(submissionResponse.NotCreated) > 0 { @@ -747,13 +778,15 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string var setResponse EmailSetResponse err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse) if err != nil { - return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } var getResponse EmailSubmissionGetResponse err = retrieveResponseMatchParameters(body, CommandEmailSubmissionGet, "1", &getResponse) if err != nil { - return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(getResponse.List) != 1 { diff --git a/pkg/jmap/jmap_api_identity.go b/pkg/jmap/jmap_api_identity.go index fbc2a5ca4..479e1da32 100644 --- a/pkg/jmap/jmap_api_identity.go +++ b/pkg/jmap/jmap_api_identity.go @@ -21,16 +21,21 @@ func (j *Client) GetIdentity(accountId string, session *Session, ctx context.Con logger = j.logger(aid, "GetIdentity", session, logger) cmd, err := request(invocation(CommandIdentityGet, IdentityGetCommand{AccountId: aid}, "0")) if err != nil { - return Identities{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return Identities{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Identities, Error) { var response IdentityGetResponse err = retrieveResponseMatchParameters(body, CommandIdentityGet, "0", &response) + if err != nil { + logger.Error().Err(err) + return Identities{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) + } return Identities{ Identities: response.List, State: response.State, SessionState: body.SessionState, - }, simpleError(err, JmapErrorInvalidJmapResponsePayload) + }, nil }) } @@ -55,7 +60,8 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex cmd, err := request(calls...) if err != nil { - return IdentitiesGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return IdentitiesGetResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (IdentitiesGetResponse, Error) { identities := make(map[string][]Identity, len(uniqueAccountIds)) @@ -65,6 +71,7 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex var response IdentityGetResponse err = retrieveResponseMatchParameters(body, CommandIdentityGet, strconv.Itoa(i), &response) if err != nil { + logger.Error().Err(err) return IdentitiesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } else { identities[accountId] = response.List @@ -81,3 +88,63 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex }, nil }) } + +type IdentitiesAndMailboxesGetResponse struct { + Identities map[string][]Identity `json:"identities,omitempty"` + NotFound []string `json:"notFound,omitempty"` + State string `json:"state"` + SessionState string `json:"sessionState"` + Mailboxes []Mailbox `json:"mailboxes"` +} + +func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (IdentitiesAndMailboxesGetResponse, Error) { + uniqueAccountIds := structs.Uniq(accountIds) + + logger = j.loggerParams("", "GetIdentitiesAndMailboxes", session, logger, func(l zerolog.Context) zerolog.Context { + return l.Array(logAccountId, log.SafeStringArray(uniqueAccountIds)) + }) + + calls := make([]Invocation, len(uniqueAccountIds)+1) + calls[0] = invocation(CommandMailboxGet, MailboxGetCommand{AccountId: mailboxAccountId}, "0") + for i, accountId := range uniqueAccountIds { + calls[i+1] = invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i+1)) + } + + cmd, err := request(calls...) + if err != nil { + logger.Error().Err(err) + return IdentitiesAndMailboxesGetResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) + } + return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (IdentitiesAndMailboxesGetResponse, Error) { + identities := make(map[string][]Identity, len(uniqueAccountIds)) + lastState := "" + notFound := []string{} + for i, accountId := range uniqueAccountIds { + var response IdentityGetResponse + err = retrieveResponseMatchParameters(body, CommandIdentityGet, strconv.Itoa(i+1), &response) + if err != nil { + logger.Error().Err(err) + return IdentitiesAndMailboxesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) + } else { + identities[accountId] = response.List + } + lastState = response.State + notFound = append(notFound, response.NotFound...) + } + + var mailboxResponse MailboxGetResponse + err = retrieveResponseMatchParameters(body, CommandMailboxGet, "0", &mailboxResponse) + if err != nil { + logger.Error().Err(err) + return IdentitiesAndMailboxesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) + } + + return IdentitiesAndMailboxesGetResponse{ + Identities: identities, + NotFound: structs.Uniq(notFound), + State: lastState, + SessionState: body.SessionState, + Mailboxes: mailboxResponse.List, + }, nil + }) +} diff --git a/pkg/jmap/jmap_api_mailbox.go b/pkg/jmap/jmap_api_mailbox.go index 5adef17aa..257e644c8 100644 --- a/pkg/jmap/jmap_api_mailbox.go +++ b/pkg/jmap/jmap_api_mailbox.go @@ -19,13 +19,15 @@ func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Cont logger = j.logger(aid, "GetMailbox", session, logger) cmd, err := request(invocation(CommandMailboxGet, MailboxGetCommand{AccountId: aid, Ids: ids}, "0")) if err != nil { - return MailboxesResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return MailboxesResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (MailboxesResponse, Error) { var response MailboxGetResponse err = retrieveResponseMatchParameters(body, CommandMailboxGet, "0", &response) if err != nil { + logger.Error().Err(err) return MailboxesResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } @@ -77,14 +79,16 @@ func (j *Client) SearchMailboxes(accountId string, session *Session, ctx context }, "1"), ) if err != nil { - return Mailboxes{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return Mailboxes{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Mailboxes, Error) { var response MailboxGetResponse err = retrieveResponseMatchParameters(body, CommandMailboxGet, "1", &response) if err != nil { - return Mailboxes{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err} + logger.Error().Err(err) + return Mailboxes{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } return Mailboxes{Mailboxes: response.List, State: response.State, SessionState: body.SessionState}, nil }) diff --git a/pkg/jmap/jmap_api_vacation.go b/pkg/jmap/jmap_api_vacation.go index 09c0fe5c1..cd2867d2f 100644 --- a/pkg/jmap/jmap_api_vacation.go +++ b/pkg/jmap/jmap_api_vacation.go @@ -18,12 +18,17 @@ func (j *Client) GetVacationResponse(accountId string, session *Session, ctx con logger = j.logger(aid, "GetVacationResponse", session, logger) cmd, err := request(invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: aid}, "0")) if err != nil { - return VacationResponseGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return VacationResponseGetResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (VacationResponseGetResponse, Error) { var response VacationResponseGetResponse err = retrieveResponseMatchParameters(body, CommandVacationResponseGet, "0", &response) - return response, simpleError(err, JmapErrorInvalidJmapResponsePayload) + if err != nil { + logger.Error().Err(err) + return VacationResponseGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) + } + return response, nil }) } @@ -80,29 +85,34 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: aid}, "1"), ) if err != nil { - return VacationResponseChange{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err} + logger.Error().Err(err) + return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapRequestPayload) } return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (VacationResponseChange, Error) { var setResponse VacationResponseSetResponse err = retrieveResponseMatchParameters(body, CommandVacationResponseSet, "0", &setResponse) if err != nil { + logger.Error().Err(err) return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } setErr, notok := setResponse.NotCreated[vacationResponseId] if notok { // this means that the VacationResponse was not updated + logger.Error().Msgf("%T.NotCreated contains an error: %v", setResponse, setErr) return VacationResponseChange{}, setErrorError(setErr, VacationResponseType) } var getResponse VacationResponseGetResponse err = retrieveResponseMatchParameters(body, CommandVacationResponseGet, "1", &getResponse) if err != nil { + logger.Error().Err(err) return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } if len(getResponse.List) != 1 { err = fmt.Errorf("failed to find %s in %s response", string(VacationResponseType), string(CommandVacationResponseGet)) + logger.Error().Err(err) return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload) } diff --git a/pkg/jmap/jmap_client.go b/pkg/jmap/jmap_client.go index a7a3cc260..2125e70df 100644 --- a/pkg/jmap/jmap_client.go +++ b/pkg/jmap/jmap_client.go @@ -32,9 +32,9 @@ func (j *Client) AddSessionEventListener(listener SessionEventListener) { j.sessionEventListeners.add(listener) } -func (j *Client) onSessionOutdated(session *Session) { +func (j *Client) onSessionOutdated(session *Session, newSessionState string) { j.sessionEventListeners.signal(func(listener SessionEventListener) { - listener.OnSessionOutdated(session) + listener.OnSessionOutdated(session, newSessionState) }) } diff --git a/pkg/jmap/jmap_session.go b/pkg/jmap/jmap_session.go index 0525aa1ad..26b627ea0 100644 --- a/pkg/jmap/jmap_session.go +++ b/pkg/jmap/jmap_session.go @@ -8,7 +8,7 @@ import ( ) type SessionEventListener interface { - OnSessionOutdated(session *Session) + OnSessionOutdated(session *Session, newSessionState string) } // Cached user related information diff --git a/pkg/jmap/jmap_tools.go b/pkg/jmap/jmap_tools.go index 93c1308a4..b604145f9 100644 --- a/pkg/jmap/jmap_tools.go +++ b/pkg/jmap/jmap_tools.go @@ -42,7 +42,7 @@ func command[T any](api ApiClient, logger *log.Logger, ctx context.Context, session *Session, - sessionOutdatedHandler func(session *Session), + sessionOutdatedHandler func(session *Session, newState string), request Request, mapper func(body *Response) (T, Error)) (T, Error) { @@ -62,7 +62,7 @@ func command[T any](api ApiClient, if response.SessionState != session.State { if sessionOutdatedHandler != nil { - sessionOutdatedHandler(session) + sessionOutdatedHandler(session, response.SessionState) } } diff --git a/pkg/structs/structs.go b/pkg/structs/structs.go index 471a8d10a..438395b0f 100644 --- a/pkg/structs/structs.go +++ b/pkg/structs/structs.go @@ -2,6 +2,9 @@ package structs import ( + "maps" + "slices" + orderedmap "github.com/wk8/go-ordered-map" ) @@ -30,3 +33,11 @@ func Uniq[T comparable](source []T) []T { } return set } + +func Keys[K comparable, V any](source map[K]V) []K { + if source == nil { + var zero []K + return zero + } + return slices.Collect(maps.Keys(source)) +} diff --git a/pkg/structs/structs_test.go b/pkg/structs/structs_test.go index 2df2281c1..b06a5e31a 100644 --- a/pkg/structs/structs_test.go +++ b/pkg/structs/structs_test.go @@ -83,3 +83,19 @@ func TestUniqWithStructs(t *testing.T) { }) } } + +func TestKeys(t *testing.T) { + tests := []struct { + input map[int]string + expected []int + }{ + {map[int]string{5: "cinq", 1: "un", 3: "trois", 4: "vier"}, []int{5, 1, 3, 4}}, + {map[int]string{1: "un"}, []int{1}}, + } + for i, tt := range tests { + t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) { + result := Keys(tt.input) + assert.ElementsMatch(t, tt.expected, result) + }) + } +} diff --git a/services/groupware/pkg/config/defaults/defaultconfig.go b/services/groupware/pkg/config/defaults/defaultconfig.go index d92f8f13f..a45a1a39c 100644 --- a/services/groupware/pkg/config/defaults/defaultconfig.go +++ b/services/groupware/pkg/config/defaults/defaultconfig.go @@ -35,7 +35,7 @@ func DefaultConfig() *config.Config { MaxBodyValueBytes: -1, ResponseHeaderTimeout: 10 * time.Second, SessionCache: config.MailSessionCache{ - Ttl: 30 * time.Second, + Ttl: 5 * time.Minute, FailureTtl: 15 * time.Second, MaxCapacity: 10_000, }, diff --git a/services/groupware/pkg/groupware/groupware_api_account.go b/services/groupware/pkg/groupware/groupware_api_account.go index 93be5e346..1d40f97ce 100644 --- a/services/groupware/pkg/groupware/groupware_api_account.go +++ b/services/groupware/pkg/groupware/groupware_api_account.go @@ -2,6 +2,9 @@ package groupware import ( "net/http" + + "github.com/opencloud-eu/opencloud/pkg/jmap" + "github.com/opencloud-eu/opencloud/pkg/structs" ) func (g Groupware) GetAccount(w http.ResponseWriter, r *http.Request) { @@ -19,3 +22,57 @@ func (g Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) { return response(req.session.Accounts, req.session.State) }) } + +type AccountBootstrapResponse struct { + // The API version. + Version string `json:"version"` + + // A list of capabilities of this API version. + Capabilities []string `json:"capabilities"` + + // API limits. + Limits IndexLimits `json:"limits"` + + // Accounts that are available to the user. + // + // The key of the mapis the identifier. + Accounts map[string]IndexAccount `json:"accounts"` + + // Primary accounts for usage types. + PrimaryAccounts IndexPrimaryAccounts `json:"primaryAccounts"` + + // Mailboxes. + Mailboxes map[string][]jmap.Mailbox `json:"mailboxes"` +} + +// When the request suceeds. +// swagger:response IndexResponse +type SwaggerAccountBootstrapResponse struct { + // in: body + Body struct { + *AccountBootstrapResponse + } +} + +func (g Groupware) GetAccountBootstrap(w http.ResponseWriter, r *http.Request) { + g.respond(w, r, func(req Request) Response { + mailAccountId := req.GetAccountId() + accountIds := structs.Keys(req.session.Accounts) + + resp, jerr := g.jmap.GetIdentitiesAndMailboxes(mailAccountId, accountIds, req.session, req.ctx, req.logger) + if jerr != nil { + return req.errorResponseFromJmap(jerr) + } + + return response(AccountBootstrapResponse{ + Version: Version, + Capabilities: Capabilities, + Limits: buildIndexLimits(req.session), + Accounts: buildIndexAccount(req.session, resp.Identities), + PrimaryAccounts: buildIndexPrimaryAccounts(req.session), + Mailboxes: map[string][]jmap.Mailbox{ + mailAccountId: resp.Mailboxes, + }, + }, resp.SessionState) + }) +} diff --git a/services/groupware/pkg/groupware/groupware_api_index.go b/services/groupware/pkg/groupware/groupware_api_index.go index c289958f8..25be9ea25 100644 --- a/services/groupware/pkg/groupware/groupware_api_index.go +++ b/services/groupware/pkg/groupware/groupware_api_index.go @@ -150,66 +150,70 @@ type SwaggerIndexResponse struct { // 200: IndexResponse func (g Groupware) Index(w http.ResponseWriter, r *http.Request) { g.respond(w, r, func(req Request) Response { - - accountIds := make([]string, len(req.session.Accounts)) - i := 0 - for k := range req.session.Accounts { - accountIds[i] = k - i++ - } - accountIds = structs.Uniq(accountIds) + accountIds := structs.Keys(req.session.Accounts) identitiesResponse, err := g.jmap.GetIdentities(accountIds, req.session, req.ctx, req.logger) if err != nil { return req.errorResponseFromJmap(err) } - accounts := make(map[string]IndexAccount, len(req.session.Accounts)) - for accountId, account := range req.session.Accounts { - indexAccount := IndexAccount{ - Name: account.Name, - IsPersonal: account.IsPersonal, - IsReadOnly: account.IsReadOnly, - Capabilities: IndexAccountCapabilities{ - Mail: IndexAccountMailCapabilities{ - MaxMailboxDepth: account.AccountCapabilities.Mail.MaxMailboxDepth, - MaxSizeMailboxName: account.AccountCapabilities.Mail.MaxSizeMailboxName, - MaxMailboxesPerEmail: account.AccountCapabilities.Mail.MaxMailboxesPerEmail, - MaxSizeAttachmentsPerEmail: account.AccountCapabilities.Mail.MaxSizeAttachmentsPerEmail, - MayCreateTopLevelMailbox: account.AccountCapabilities.Mail.MayCreateTopLevelMailbox, - MaxDelayedSend: account.AccountCapabilities.Submission.MaxDelayedSend, - }, - Sieve: IndexAccountSieveCapabilities{ - MaxSizeScriptName: account.AccountCapabilities.Sieve.MaxSizeScript, - MaxSizeScript: account.AccountCapabilities.Sieve.MaxSizeScript, - MaxNumberScripts: account.AccountCapabilities.Sieve.MaxNumberScripts, - MaxNumberRedirects: account.AccountCapabilities.Sieve.MaxNumberRedirects, - }, - }, - } - if identity, ok := identitiesResponse.Identities[accountId]; ok { - indexAccount.Identities = identity - } - accounts[accountId] = indexAccount - } - return response(IndexResponse{ - Version: Version, - Capabilities: Capabilities, - Limits: IndexLimits{ - MaxSizeUpload: req.session.Capabilities.Core.MaxSizeUpload, - MaxConcurrentUpload: req.session.Capabilities.Core.MaxConcurrentUpload, - MaxSizeRequest: req.session.Capabilities.Core.MaxSizeRequest, - MaxConcurrentRequests: req.session.Capabilities.Core.MaxConcurrentRequests, - }, - Accounts: accounts, - PrimaryAccounts: IndexPrimaryAccounts{ - Mail: req.session.PrimaryAccounts.Mail, - Submission: req.session.PrimaryAccounts.Submission, - Blob: req.session.PrimaryAccounts.Blob, - VacationResponse: req.session.PrimaryAccounts.VacationResponse, - Sieve: req.session.PrimaryAccounts.Sieve, - }, + Version: Version, + Capabilities: Capabilities, + Limits: buildIndexLimits(req.session), + Accounts: buildIndexAccount(req.session, identitiesResponse.Identities), + PrimaryAccounts: buildIndexPrimaryAccounts(req.session), }, req.session.State) }) } + +func buildIndexLimits(session *jmap.Session) IndexLimits { + return IndexLimits{ + MaxSizeUpload: session.Capabilities.Core.MaxSizeUpload, + MaxConcurrentUpload: session.Capabilities.Core.MaxConcurrentUpload, + MaxSizeRequest: session.Capabilities.Core.MaxSizeRequest, + MaxConcurrentRequests: session.Capabilities.Core.MaxConcurrentRequests, + } +} + +func buildIndexPrimaryAccounts(session *jmap.Session) IndexPrimaryAccounts { + return IndexPrimaryAccounts{ + Mail: session.PrimaryAccounts.Mail, + Submission: session.PrimaryAccounts.Submission, + Blob: session.PrimaryAccounts.Blob, + VacationResponse: session.PrimaryAccounts.VacationResponse, + Sieve: session.PrimaryAccounts.Sieve, + } +} + +func buildIndexAccount(session *jmap.Session, identities map[string][]jmap.Identity) map[string]IndexAccount { + accounts := make(map[string]IndexAccount, len(session.Accounts)) + for accountId, account := range session.Accounts { + indexAccount := IndexAccount{ + Name: account.Name, + IsPersonal: account.IsPersonal, + IsReadOnly: account.IsReadOnly, + Capabilities: IndexAccountCapabilities{ + Mail: IndexAccountMailCapabilities{ + MaxMailboxDepth: account.AccountCapabilities.Mail.MaxMailboxDepth, + MaxSizeMailboxName: account.AccountCapabilities.Mail.MaxSizeMailboxName, + MaxMailboxesPerEmail: account.AccountCapabilities.Mail.MaxMailboxesPerEmail, + MaxSizeAttachmentsPerEmail: account.AccountCapabilities.Mail.MaxSizeAttachmentsPerEmail, + MayCreateTopLevelMailbox: account.AccountCapabilities.Mail.MayCreateTopLevelMailbox, + MaxDelayedSend: account.AccountCapabilities.Submission.MaxDelayedSend, + }, + Sieve: IndexAccountSieveCapabilities{ + MaxSizeScriptName: account.AccountCapabilities.Sieve.MaxSizeScript, + MaxSizeScript: account.AccountCapabilities.Sieve.MaxSizeScript, + MaxNumberScripts: account.AccountCapabilities.Sieve.MaxNumberScripts, + MaxNumberRedirects: account.AccountCapabilities.Sieve.MaxNumberRedirects, + }, + }, + } + if identity, ok := identities[accountId]; ok { + indexAccount.Identities = identity + } + accounts[accountId] = indexAccount + } + return accounts +} diff --git a/services/groupware/pkg/groupware/groupware_api_messages.go b/services/groupware/pkg/groupware/groupware_api_messages.go index 880594fda..f3343a3d9 100644 --- a/services/groupware/pkg/groupware/groupware_api_messages.go +++ b/services/groupware/pkg/groupware/groupware_api_messages.go @@ -411,11 +411,7 @@ type MessageCreation struct { func (g Groupware) CreateMessage(w http.ResponseWriter, r *http.Request) { g.respond(w, r, func(req Request) Response { - messageId := chi.URLParam(r, UriParamMessageId) - - l := req.logger.With() - l.Str(UriParamMessageId, messageId) - logger := log.From(l) + logger := req.logger var body MessageCreation err := req.body(&body) diff --git a/services/groupware/pkg/groupware/groupware_framework.go b/services/groupware/pkg/groupware/groupware_framework.go index e6c3651b8..6f34028ef 100644 --- a/services/groupware/pkg/groupware/groupware_framework.go +++ b/services/groupware/pkg/groupware/groupware_framework.go @@ -63,13 +63,16 @@ func (e GroupwareInitializationError) Unwrap() error { } type GroupwareSessionEventListener struct { + logger *log.Logger sessionCache *ttlcache.Cache[string, cachedSession] } -func (l GroupwareSessionEventListener) OnSessionOutdated(session *jmap.Session) { +func (l GroupwareSessionEventListener) OnSessionOutdated(session *jmap.Session, newSessionState string) { // it's enough to remove the session from the cache, as it will be fetched on-demand // the next time an operation is performed on behalf of the user l.sessionCache.Delete(session.Username) + + l.logger.Trace().Msgf("removed outdated session for user '%v': state %s -> %s", session.Username, session.State, newSessionState) } var _ jmap.SessionEventListener = GroupwareSessionEventListener{} @@ -134,7 +137,28 @@ func NewGroupware(config *config.Config, logger *log.Logger, mux *chi.Mux) (*Gro go sessionCache.Start() } - sessionEventListener := GroupwareSessionEventListener{sessionCache: sessionCache} + if logger.Trace().Enabled() { + sessionCache.OnEviction(func(c context.Context, r ttlcache.EvictionReason, item *ttlcache.Item[string, cachedSession]) { + reason := "" + switch r { + case ttlcache.EvictionReasonDeleted: + reason = "deleted" + case ttlcache.EvictionReasonCapacityReached: + reason = "capacity reached" + case ttlcache.EvictionReasonExpired: + reason = fmt.Sprintf("expired after %vms", item.TTL().Milliseconds()) + case ttlcache.EvictionReasonMaxCostExceeded: + reason = "max cost exceeded" + } + if reason == "" { + reason = fmt.Sprintf("unknown (%v)", r) + } + + logger.Trace().Msgf("session cache eviction of user '%v': %v", item.Key(), reason) + }) + } + + sessionEventListener := GroupwareSessionEventListener{sessionCache: sessionCache, logger: logger} jmapClient.AddSessionEventListener(&sessionEventListener) return &Groupware{ @@ -388,7 +412,7 @@ func (g Groupware) serveError(w http.ResponseWriter, r *http.Request, error *Err render.Render(w, r, errorResponses(*error)) } -func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(r Request) Response) { +func (g Groupware) withSession(w http.ResponseWriter, r *http.Request, handler func(r Request) Response) (Response, bool) { ctx := r.Context() sl := g.logger.SubloggerWithRequestID(ctx) logger := &sl @@ -396,11 +420,11 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func( username, ok, err := g.usernameProvider.GetUsername(r, ctx, logger) if err != nil { g.serveError(w, r, apiError(errorId(r, ctx), ErrorInvalidAuthentication)) - return + return Response{}, false } if !ok { g.serveError(w, r, apiError(errorId(r, ctx), ErrorMissingAuthentication)) - return + return Response{}, false } logger = log.From(logger.With().Str(logUsername, log.SafeString(username))) @@ -409,13 +433,13 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func( if err != nil { logger.Error().Err(err).Interface(logQuery, r.URL.Query()).Msg("failed to determine JMAP session") render.Status(r, http.StatusInternalServerError) - return + return Response{}, false } if !ok { // no session = authentication failed logger.Warn().Err(err).Interface(logQuery, r.URL.Query()).Msg("could not authenticate") render.Status(r, http.StatusForbidden) - return + return Response{}, false } decoratedLogger := session.DecorateLogger(*logger) @@ -427,6 +451,10 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func( } response := handler(req) + return response, true +} + +func (g Groupware) sendResponse(w http.ResponseWriter, r *http.Request, response Response) { if response.err != nil { g.log(response.err) w.Header().Add("Content-Type", ContentTypeJsonApi) @@ -456,6 +484,14 @@ func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func( } } +func (g Groupware) respond(w http.ResponseWriter, r *http.Request, handler func(r Request) Response) { + response, ok := g.withSession(w, r, handler) + if !ok { + return + } + g.sendResponse(w, r, response) +} + func (g Groupware) stream(w http.ResponseWriter, r *http.Request, handler func(r Request, w http.ResponseWriter) *Error) { ctx := r.Context() sl := g.logger.SubloggerWithRequestID(ctx) diff --git a/services/groupware/pkg/groupware/groupware_route.go b/services/groupware/pkg/groupware/groupware_route.go index 34355ced5..87a69821c 100644 --- a/services/groupware/pkg/groupware/groupware_route.go +++ b/services/groupware/pkg/groupware/groupware_route.go @@ -42,6 +42,7 @@ func (g Groupware) Route(r chi.Router) { r.Get("/accounts", g.GetAccounts) r.Route("/accounts/{accountid}", func(r chi.Router) { r.Get("/", g.GetAccount) + r.Get("/bootstrap", g.GetAccountBootstrap) r.Get("/identities", g.GetIdentities) r.Get("/vacation", g.GetVacation) r.Put("/vacation", g.SetVacation) diff --git a/services/groupware/pkg/groupware/groupware_session.go b/services/groupware/pkg/groupware/groupware_session.go index 7a83bdda1..e7e27d623 100644 --- a/services/groupware/pkg/groupware/groupware_session.go +++ b/services/groupware/pkg/groupware/groupware_session.go @@ -59,7 +59,7 @@ func (l *sessionCacheLoader) Load(c *ttlcache.Cache[string, cachedSession], user return c.Set(username, failedSession{err: err}, l.errorTtl) } else { l.logger.Debug().Str("username", username).Msgf("successfully created session for '%v'", username) - return c.Set(username, succeededSession{session: session}, 0) // 0 = use the TTL configured on the Cache + return c.Set(username, succeededSession{session: session}, ttlcache.DefaultTTL) // use the TTL configured on the Cache } }