1
0
mirror of https://github.com/lus/pasty.git synced 2023-08-10 21:13:09 +03:00

Rename the deletion token to modification token

This commit is contained in:
Lukas Schulte Pelkum 2021-06-28 18:35:30 +02:00
parent 4cf982bb23
commit f61c98af71
No known key found for this signature in database
GPG Key ID: 408DA7CA81DB885C
11 changed files with 113 additions and 65 deletions

View File

@ -1,6 +1,21 @@
# pasty
Pasty is a fast and lightweight code pasting server
## !!! Important deprecation notices !!!
> This version of pasty uses a new field name for the so far called `deletionToken`: `modificationToken`.
> Instances using **PostgreSQL** are **not affected** as a corresponding SQL migration will run before the first startup.
> If you however use **another storage driver** you may have to **update the entries** by hand or using a simple query, depending on your driver as I don't plan to ship migrations for every single storage driver.
> It may be important to know that the **data migrator has been upgraded** too. This may serve as a **convenient workaround** (export data (field will be renamed) and import data with changed field names again).
> Additionally, I changed the three `DELETION_TOKEN*`environment variables to their corresponding `MODIFICATION_TOKEN*` ones:
> - `DELETION_TOKENS` -> `MODIFICATION_TOKENS`
> - `DELETION_TOKEN_MASTER` -> `MODIFICATION_TOKEN_MASTER`
> - `DELETION_TOKEN_LENGTH` -> `MODIFICATION_TOKEN_LENGTH`
>
> Again, **the old ones will still work** because I do not want to jumble your configurations. However, **please consider updating** them to stay future-proof ^^.
## Support
As pasty is an open source project on GitHub you can open an [issue](https://github.com/lus/pasty/issues) whenever you encounter a problem or feature request.
@ -55,17 +70,17 @@ Pasty will be available at http://localhost:8080.
---
## General environment variables
| Environment Variable | Default Value | Type | Description |
|-------------------------------|---------------|----------|--------------------------------------------------------------------------------------------------------------------|
| `PASTY_WEB_ADDRESS` | `:8080` | `string` | Defines the address the web server listens to |
| `PASTY_STORAGE_TYPE` | `file` | `string` | Defines the storage type the pastes are saved to |
| `PASTY_HASTEBIN_SUPPORT` | `false` | `bool` | Defines whether or not the `POST /documents` endpoint should be enabled, as known from the hastebin servers |
| `PASTY_ID_LENGTH` | `6` | `number` | Defines the length of the ID of a paste |
| `PASTY_DELETION_TOKENS` | `true` | `bool` | Defines whether or not deletion tokens should be generated |
| `PASTY_DELETION_TOKEN_MASTER` | `<empty>` | `string` | Defines the master deletion token which is authorized to delete every paste (even if deletion tokens are disabled) |
| `PASTY_DELETION_TOKEN_LENGTH` | `12` | `number` | Defines the length of the deletion token of a paste |
| `PASTY_RATE_LIMIT` | `30-M` | `string` | Defines the rate limit of the API (see https://github.com/ulule/limiter#usage) |
| `PASTY_LENGTH_CAP` | `50000` | `number` | Defines the maximum amount of characters a paste is allowed to contain (a value `<= 0` means no limit) |
| Environment Variable | Default Value | Type | Description |
|-----------------------------------|---------------|----------|----------------------------------------------------------------------------------------------------------------------------|
| `PASTY_WEB_ADDRESS` | `:8080` | `string` | Defines the address the web server listens to |
| `PASTY_STORAGE_TYPE` | `file` | `string` | Defines the storage type the pastes are saved to |
| `PASTY_HASTEBIN_SUPPORT` | `false` | `bool` | Defines whether or not the `POST /documents` endpoint should be enabled, as known from the hastebin servers |
| `PASTY_ID_LENGTH` | `6` | `number` | Defines the length of the ID of a paste |
| `PASTY_MODIFICATION_TOKENS` | `true` | `bool` | Defines whether or not modification tokens should be generated |
| `PASTY_MODIFICATION_TOKEN_MASTER` | `` | `string` | Defines the master modification token which is authorized to modify every paste (even if modification tokens are disabled) |
| `PASTY_MODIFICATION_TOKEN_LENGTH` | `12` | `number` | Defines the length of the modification token of a paste |
| `PASTY_RATE_LIMIT` | `30-M` | `string` | Defines the rate limit of the API (see https://github.com/ulule/limiter#usage) |
| `PASTY_LENGTH_CAP` | `50000` | `number` | Defines the maximum amount of characters a paste is allowed to contain (a value `<= 0` means no limit) |
## AutoDelete
Pasty provides an intuitive system to automatically delete pastes after a specific amount of time. You can configure it with the following variables:

View File

@ -56,6 +56,15 @@ func main() {
continue
}
// Move the content of the deletion token field to the modification field
if paste.DeletionToken != "" {
if paste.ModificationToken == "" {
paste.ModificationToken = paste.DeletionToken
}
paste.DeletionToken = ""
log.Println("[INFO] Paste " + id + " was a legacy one.")
}
// Save the paste
err = to.Save(paste)
if err != nil {

View File

@ -10,20 +10,20 @@ import (
// Config represents the general application configuration structure
type Config struct {
WebAddress string
StorageType shared.StorageType
HastebinSupport bool
IDLength int
DeletionTokens bool
DeletionTokenMaster string
DeletionTokenLength int
RateLimit string
LengthCap int
AutoDelete *AutoDeleteConfig
File *FileConfig
Postgres *PostgresConfig
MongoDB *MongoDBConfig
S3 *S3Config
WebAddress string
StorageType shared.StorageType
HastebinSupport bool
IDLength int
ModificationTokens bool
ModificationTokenMaster string
ModificationTokenLength int
RateLimit string
LengthCap int
AutoDelete *AutoDeleteConfig
File *FileConfig
Postgres *PostgresConfig
MongoDB *MongoDBConfig
S3 *S3Config
}
// AutoDeleteConfig represents the configuration specific for the AutoDelete behaviour
@ -69,15 +69,15 @@ func Load() {
env.Load()
Current = &Config{
WebAddress: env.MustString("WEB_ADDRESS", ":8080"),
StorageType: shared.StorageType(strings.ToLower(env.MustString("STORAGE_TYPE", "file"))),
HastebinSupport: env.MustBool("HASTEBIN_SUPPORT", false),
IDLength: env.MustInt("ID_LENGTH", 6),
DeletionTokens: env.MustBool("DELETION_TOKENS", true),
DeletionTokenMaster: env.MustString("DELETION_TOKEN_MASTER", ""),
DeletionTokenLength: env.MustInt("DELETION_TOKEN_LENGTH", 12),
RateLimit: env.MustString("RATE_LIMIT", "30-M"),
LengthCap: env.MustInt("LENGTH_CAP", 50_000),
WebAddress: env.MustString("WEB_ADDRESS", ":8080"),
StorageType: shared.StorageType(strings.ToLower(env.MustString("STORAGE_TYPE", "file"))),
HastebinSupport: env.MustBool("HASTEBIN_SUPPORT", false),
IDLength: env.MustInt("ID_LENGTH", 6),
ModificationTokens: env.MustBool("MODIFICATION_TOKENS", env.MustBool("DELETION_TOKENS", true)), // ---
ModificationTokenMaster: env.MustString("MODIFICATION_TOKEN_MASTER", env.MustString("DELETION_TOKEN_MASTER", "")), // - We don't want to destroy peoples old configuration
ModificationTokenLength: env.MustInt("MODIFICATION_TOKEN_LENGTH", env.MustInt("DELETION_TOKEN_LENGTH", 12)), // ---
RateLimit: env.MustString("RATE_LIMIT", "30-M"),
LengthCap: env.MustInt("LENGTH_CAP", 50_000),
AutoDelete: &AutoDeleteConfig{
Enabled: env.MustBool("AUTODELETE", false),
Lifetime: env.MustDuration("AUTODELETE_LIFETIME", 720*time.Hour),

View File

@ -1,30 +1,42 @@
package shared
import (
"log"
"github.com/alexedwards/argon2id"
)
// Paste represents a saved paste
type Paste struct {
ID string `json:"id" bson:"_id"`
Content string `json:"content" bson:"content"`
DeletionToken string `json:"deletionToken,omitempty" bson:"deletionToken"`
Created int64 `json:"created" bson:"created"`
AutoDelete bool `json:"autoDelete" bson:"autoDelete"`
ID string `json:"id" bson:"_id"`
Content string `json:"content" bson:"content"`
DeletionToken string `json:"deletionToken,omitempty" bson:"deletionToken"` // Required for legacy paste storage support
ModificationToken string `json:"modificationToken,omitempty" bson:"modificationToken"`
Created int64 `json:"created" bson:"created"`
AutoDelete bool `json:"autoDelete" bson:"autoDelete"`
}
// HashDeletionToken hashes the current deletion token of a paste
func (paste *Paste) HashDeletionToken() error {
hash, err := argon2id.CreateHash(paste.DeletionToken, argon2id.DefaultParams)
// HashModificationToken hashes the current modification token of a paste
func (paste *Paste) HashModificationToken() error {
hash, err := argon2id.CreateHash(paste.ModificationToken, argon2id.DefaultParams)
if err != nil {
return err
}
paste.DeletionToken = hash
paste.ModificationToken = hash
return nil
}
// CheckDeletionToken checks whether or not the given deletion token is correct
func (paste *Paste) CheckDeletionToken(deletionToken string) bool {
match, err := argon2id.ComparePasswordAndHash(deletionToken, paste.DeletionToken)
// CheckModificationToken checks whether or not the given modification token is correct
func (paste *Paste) CheckModificationToken(modificationToken string) bool {
// The modification token may be stored in the deletion token field in old pastes
usedToken := paste.ModificationToken
if usedToken == "" {
usedToken = paste.DeletionToken
if usedToken != "" {
log.Println("WARNING: You seem to have pastes with the old 'deletionToken' field stored in your storage driver. Though this does not cause any issues right now, it may in the future. Consider some kind of migration.")
}
}
match, err := argon2id.ComparePasswordAndHash(modificationToken, usedToken)
return err == nil && match
}

View File

@ -0,0 +1,5 @@
begin;
alter table if exists "pastes" rename column "modificationToken" to "deletionToken";
commit;

View File

@ -0,0 +1,5 @@
begin;
alter table if exists "pastes" rename column "deletionToken" to "modificationToken";
commit;

View File

@ -82,7 +82,7 @@ func (driver *PostgresDriver) Get(id string) (*shared.Paste, error) {
row := driver.pool.QueryRow(context.Background(), query, id)
paste := new(shared.Paste)
if err := row.Scan(&paste.ID, &paste.Content, &paste.DeletionToken, &paste.Created, &paste.AutoDelete); err != nil {
if err := row.Scan(&paste.ID, &paste.Content, &paste.ModificationToken, &paste.Created, &paste.AutoDelete); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
@ -95,7 +95,7 @@ func (driver *PostgresDriver) Get(id string) (*shared.Paste, error) {
func (driver *PostgresDriver) Save(paste *shared.Paste) error {
query := "INSERT INTO pastes VALUES ($1, $2, $3, $4, $5)"
_, err := driver.pool.Exec(context.Background(), query, paste.ID, paste.Content, paste.DeletionToken, paste.Created, paste.AutoDelete)
_, err := driver.pool.Exec(context.Background(), query, paste.ID, paste.Content, paste.ModificationToken, paste.Created, paste.AutoDelete)
return err
}

View File

@ -50,11 +50,11 @@ func HastebinSupportHandler(ctx *fasthttp.RequestCtx) {
AutoDelete: config.Current.AutoDelete.Enabled,
}
// Set a deletion token
if config.Current.DeletionTokens {
paste.DeletionToken = utils.RandomString(config.Current.DeletionTokenLength)
// Set a modification token
if config.Current.ModificationTokens {
paste.ModificationToken = utils.RandomString(config.Current.ModificationTokenLength)
err = paste.HashDeletionToken()
err = paste.HashModificationToken()
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.SetBodyString(err.Error())

View File

@ -38,6 +38,7 @@ func v1GetPaste(ctx *fasthttp.RequestCtx) {
return
}
paste.DeletionToken = ""
paste.ModificationToken = ""
// Respond with the paste
jsonData, err := json.Marshal(paste)
@ -91,13 +92,13 @@ func v1PostPaste(ctx *fasthttp.RequestCtx) {
AutoDelete: config.Current.AutoDelete.Enabled,
}
// Set a deletion token
deletionToken := ""
if config.Current.DeletionTokens {
deletionToken = utils.RandomString(config.Current.DeletionTokenLength)
paste.DeletionToken = deletionToken
// Set a modification token
modificationToken := ""
if config.Current.ModificationTokens {
modificationToken = utils.RandomString(config.Current.ModificationTokenLength)
paste.ModificationToken = modificationToken
err = paste.HashDeletionToken()
err = paste.HashModificationToken()
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.SetBodyString(err.Error())
@ -115,7 +116,8 @@ func v1PostPaste(ctx *fasthttp.RequestCtx) {
// Respond with the paste
pasteCopy := *paste
pasteCopy.DeletionToken = deletionToken
pasteCopy.DeletionToken = modificationToken
pasteCopy.ModificationToken = ""
jsonData, err := json.Marshal(pasteCopy)
if err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
@ -139,9 +141,9 @@ func v1DeletePaste(ctx *fasthttp.RequestCtx) {
return
}
// Validate the deletion token of the paste
deletionToken := values["deletionToken"]
if deletionToken == "" {
// Validate the modification token of the paste
modificationToken := values["deletionToken"]
if modificationToken == "" {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetBodyString("missing 'deletionToken' field")
return
@ -160,8 +162,8 @@ func v1DeletePaste(ctx *fasthttp.RequestCtx) {
return
}
// Check if the deletion token is correct
if (config.Current.DeletionTokenMaster == "" || deletionToken != config.Current.DeletionTokenMaster) && !paste.CheckDeletionToken(deletionToken) {
// Check if the modification token is correct
if (config.Current.ModificationTokenMaster == "" || modificationToken != config.Current.ModificationTokenMaster) && !paste.CheckModificationToken(modificationToken) {
ctx.SetStatusCode(fasthttp.StatusForbidden)
ctx.SetBodyString("invalid deletion token")
return

View File

@ -61,7 +61,7 @@ func Serve() error {
v1Route.GET("/info", func(ctx *fasthttp.RequestCtx) {
jsonData, _ := json.Marshal(map[string]interface{}{
"version": static.Version,
"deletionTokens": config.Current.DeletionTokens,
"deletionTokens": config.Current.ModificationTokens,
})
ctx.SetBody(jsonData)
})