This is only the first part. When a totp_secret exists for a user, the
authentication then asks for the code and carries on.
The totp lib can handle 6 or 8 letter codes and sha1, sha256 and sha512.
For maximum compatibility with Google Authenticator though, it sticks
to 6 character and sha1.
- PUT /api/profile/password (was undocumented and not used)
- POST /api/auth
With the introduction of MFA, you can't authenticate with a username
and password anymore. OAuth is now the only way to obtain an access
token.
The password update through the API was not documented so quite safe
to remove.
- removed log noise (not authenticated)
- on /login, go to redirect when a session already exists
- in SessionAuthProvider, clear session when external while forwarded
authentication is disabled
Authentication providers are very much tied to server operations and
function and having them in internal/auth made it impossible to deal
with the session cookie handler when we need it.
internal/auth depended on the request ID for logging, removed this as
well with a specific logger interface.
Introducing a server.SessionHandler function to retrieve the cookie
handler so we can create cookies from scratch when needed.
- Dynamic Client Registration (RFC 7591)
- Dynamic Client Registration Management (RFC 7592)
- OAuth2 Authorization Code Grant (RFC 6749)
- OAuth 2.0 Token Revocation (RFC 7009)
- OAuth 2.0 Authorization Server Metadata (RFC 8414)
This introduces new routes as follow:
- GET /.well-known/oauth-authorization-server: authorization server metadata
- GET /authorize : authorization page
- POST /authorize : authorization redirect
- POST /api/oauth/client : client creation
- GET /api/oauth/client/{id} : client information
- PUT /api/oauth/client/{id} : client update
- DELETE /api/oauth/client/{id} : client removal
- POST /api/oauth/token : token retrieval
- POST /api/oauth/revoke: token revocation
Other considerations:
- The authorization flow MUST use PKCE with S256 (plain is not allowed).
- At least one scope is mandatory.
- There is no refresh token.
- There is no client_secret.
This commit also moves the API token signing to config.SigningKey
with corresponding methods. (This type, with another key) is also used
for client registration management tokens)
This aims to replace the token based CSRF protection with a modern
and more simple approach, sugin Fetch metadata and Origin header.
See https://words.filippo.io/csrf/
Every handler or middleware that doesn't need to be part of server.Server
is now a function of the server module.
This leaves only Init() and AddRoute() to server.Server.
- Added role "/email/send"
- Grant this role to unauthenticated and user group
- Adapt permission check to account for unauthenticated users
- Use WithPermission("email", "send") for recovery routes
- Use hasPermission("email", "send") in templates and removed
canSendEmail global variable
Upon startup, we check if a mail host is properly configured and remove
the role "/email/send" when it's not.
- Removed the token masking (BREACH mitigation should be dealt with at
the gzip middleware level, which is the case already)
- Make the token only valid for the current user's session (renew it
upon login and logout)
- Invalidate cache when a user logs in (new session)
Thanks to @gusted for the suggestion :)
This replaces the (bad) secret key interpolation with a set of keys
derived from the secret key, using HKDF.
- Use pkg/securecookie and pkg/csrf with configs.Keys
- Application password are hashed using a derived key with the user uid
in its context.
- API Token are simplified (MAC signed using a specific derived key)
- Shared bookmark links are encrypted using blowfish + backed2b (EtM) in
a 160-bit block
As this is a breaking change, users can now renew their application
passwords.
API Token can be retrieved on the given page. From the browser extension
a logout / login is necessary.
If the login "username" field contains a "@", we retrieve the user
with its email field instead of username. It works since "@" is not
a valid username character and "email" is a unique field in the user
table.
Added more tests for web and api login.
- golangci-lint configuration
- gofumpt on all go files
- added package comments
- added missing comments on exported functions
- do not check for bodyclose on http testing responses.
- check for errors on triggered tasks
- check for errors in acls
- check for errors during policy loading
- check for errors in password recovery process
- better error checking in pkg/extract/contentscripts
- nolint rules for errcheck when it's not needed
(mostly defer calls for file reader closing)
This was a mistake to use go.work since it might be needed on a
specific dev setup (with different "use" directives).
It results in a codebase that really matches the main readeck
module fqdn.
When unauthorized, pass the current page as a query parameter to the
login page. It will be forwarded to the login process, which will
send a redirection to the requested page upon successful authentication.
Fields and Errors properties are now private and exposed through
Fields() and Errors() methods. This will let us extend a specialized
form instance if needs be.
Also added a Form.Get(string) as a helper to retrieve a form field
instance.
Removed CSRF protection on logout route (since it's a POST, we don't
need it for this operation).
Changed the same-site value of the CSRF cookie to "lax".
The internal/session package is heavily based on gorilla session but
provides a structured payload that is serialized to JSON.
This provides a mean to load and read the session with other tools
(provided they use the same algorithm and know the keys).
- New email sender interface.
The email sender is initialized by the app prelaunch and can be
overridden later if need be. (We'll need it during tests)
- Prepared the app and server so we can run tests.
- Split init and running parts in app, server and configuration
- Properly close and delete the database connection on db.Close
- Don't panic if bookmarks.StartWorkerPool is called more than once
- Testing tools.
internal/testing provides an api for tests.
- TestApp: prepares the application so it's ready to accept requests
- Client: HTTP client to perform queries
- Response: A wrapper around http.Response with some utilities
- RunRequestSequence: to perform a series of HTTP requests
- internal/auth/signin
- internal/profile
- internal/admin tests
If a user forgets their password, we provide a form where they can
enter their account's email address. Regardless of the mail being
present in the db, we send an email with further instructions.
If the email is indeed linked to an account, the message contains a
link (valid for 2 hours) where the user can set a new password.
When successful, the user can then go to their login page.
The link for recovery (and the HTTP routes) are not available if the
mail configuration is not set.
Note: we use a very naive in-memory k/v store to keep the recovery link
codes and expire them. While it works, we might have to move to
something distributed if one day there's a need to have multiple
readeck instances running behind a load balancer. The same applies for
the cancel processes that exist elsewhere.
In order to have reusable components (with template content inside)
we had to move to a suitable template engine. No hackish {{ extends }}
anymore, blocks with arguments, includes, etc.
- Configurable cookie name and session duration
- Switched to sessions.FilesystemStore
- Store a check code based on username, email and password.
Changing any of these invalidates all the user sessions.
- Change the check code for the current user session so they don't
get kicked out on password change or profile update.
- Resend the session cookie on each request so its duration increases.
It make the cookie expires only if the user is not active during the
cookie max age.
- Added a token table. A token has a non sequential unique ID and is
attached to a user. It can be disabled and have an optional expiry
date.
- A new token can be created with a basic API using the user's
crendentials.
- The TokenAuthProvider authenticates the user when a request has
an "Authorization: Bearer {token}" header.
The JWT token is never saved in db, only the token ID. The JWT is
signed with configs.Config.Keys.JwtSk that is derived from the
configured secret key.