feat: add API tokens + force log out button

This commit is contained in:
Sam 2023-03-30 16:50:30 +02:00
parent 9c8b6a8f91
commit 2716471fa9
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
9 changed files with 207 additions and 52 deletions

View file

@ -14,6 +14,8 @@ type Token struct {
UserID xid.ID
TokenID xid.ID
Invalidated bool
APIOnly bool `db:"api_only"`
ReadOnly bool
Created time.Time
Expires time.Time
}
@ -62,10 +64,15 @@ func (db *DB) Tokens(ctx context.Context, userID xid.ID) (ts []Token, err error)
const ExpiryTime = 3 * 30 * 24 * time.Hour
// SaveToken saves a token to the database.
func (db *DB) SaveToken(ctx context.Context, userID xid.ID, tokenID xid.ID) (t Token, err error) {
func (db *DB) SaveToken(ctx context.Context, userID xid.ID, tokenID xid.ID, apiOnly, readOnly bool) (t Token, err error) {
sql, args, err := sq.Insert("tokens").
Columns("user_id", "token_id", "expires").
Values(userID, tokenID, time.Now().Add(ExpiryTime)).
SetMap(map[string]any{
"user_id": userID,
"token_id": tokenID,
"expires": time.Now().Add(ExpiryTime),
"api_only": apiOnly,
"read_only": readOnly,
}).
Suffix("RETURNING *").
ToSql()
if err != nil {

View file

@ -117,7 +117,7 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
}
// save token to database
_, err = s.DB.SaveToken(ctx, u.ID, tokenID)
_, err = s.DB.SaveToken(ctx, u.ID, tokenID, false, false)
if err != nil {
return errors.Wrap(err, "saving token to database")
}
@ -343,7 +343,7 @@ func (s *Server) discordSignup(w http.ResponseWriter, r *http.Request) error {
}
// save token to database
_, err = s.DB.SaveToken(ctx, u.ID, tokenID)
_, err = s.DB.SaveToken(ctx, u.ID, tokenID, false, false)
if err != nil {
return errors.Wrap(err, "saving token to database")
}

View file

@ -138,7 +138,7 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
}
// save token to database
_, err = s.DB.SaveToken(ctx, u.ID, tokenID)
_, err = s.DB.SaveToken(ctx, u.ID, tokenID, false, false)
if err != nil {
return errors.Wrap(err, "saving token to database")
}
@ -371,7 +371,7 @@ func (s *Server) mastodonSignup(w http.ResponseWriter, r *http.Request) error {
}
// save token to database
_, err = s.DB.SaveToken(ctx, u.ID, tokenID)
_, err = s.DB.SaveToken(ctx, u.ID, tokenID, false, false)
if err != nil {
return errors.Wrap(err, "saving token to database")
}

View file

@ -118,7 +118,7 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
}
// save token to database
_, err = s.DB.SaveToken(ctx, u.ID, tokenID)
_, err = s.DB.SaveToken(ctx, u.ID, tokenID, false, false)
if err != nil {
return errors.Wrap(err, "saving token to database")
}
@ -301,7 +301,7 @@ func (s *Server) misskeySignup(w http.ResponseWriter, r *http.Request) error {
}
// save token to database
_, err = s.DB.SaveToken(ctx, u.ID, tokenID)
_, err = s.DB.SaveToken(ctx, u.ID, tokenID, false, false)
if err != nil {
return errors.Wrap(err, "saving token to database")
}

View file

@ -12,16 +12,20 @@ import (
)
type getTokenResponse struct {
TokenID xid.ID `json:"id"`
Created time.Time `json:"created"`
Expires time.Time `json:"expires"`
TokenID xid.ID `json:"id"`
APIOnly bool `json:"api_only"`
ReadOnly bool `json:"read_only"`
Created time.Time `json:"created"`
Expires time.Time `json:"expires"`
}
func dbTokenToGetResponse(t db.Token) getTokenResponse {
return getTokenResponse{
TokenID: t.TokenID,
Created: t.Created,
Expires: t.Expires,
TokenID: t.TokenID,
APIOnly: t.APIOnly,
ReadOnly: t.ReadOnly,
Created: t.Created,
Expires: t.Expires,
}
}
@ -47,7 +51,7 @@ func (s *Server) deleteToken(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims, _ := server.ClaimsFromContext(ctx)
if !claims.TokenWrite || claims.APIToken {
if claims.APIToken {
return server.APIError{Code: server.ErrInvalidToken}
}
@ -71,7 +75,42 @@ func (s *Server) deleteToken(w http.ResponseWriter, r *http.Request) error {
return nil
}
func (s *Server) createToken(w http.ResponseWriter, r *http.Request) error {
// unimplemented right now
return server.APIError{Code: server.ErrForbidden}
type createTokenResponse struct {
Token string `json:"token"`
TokenID xid.ID `json:"id"`
APIOnly bool `json:"api_only"`
ReadOnly bool `json:"read_only"`
Created time.Time `json:"created"`
Expires time.Time `json:"expires"`
}
func (s *Server) createToken(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims, _ := server.ClaimsFromContext(ctx)
if claims.APIToken {
return server.APIError{Code: server.ErrInvalidToken}
}
readOnly := r.FormValue("read_only") == "true"
tokenID := xid.New()
tokenStr, err := s.Auth.CreateToken(claims.UserID, tokenID, false, true, !readOnly)
if err != nil {
return errors.Wrap(err, "creating token")
}
t, err := s.DB.SaveToken(ctx, claims.UserID, tokenID, true, readOnly)
if err != nil {
return errors.Wrap(err, "saving token")
}
render.JSON(w, r, createTokenResponse{
Token: tokenStr,
TokenID: t.TokenID,
APIOnly: t.APIOnly,
ReadOnly: t.ReadOnly,
Created: t.Created,
Expires: t.Expires,
})
return nil
}