Skip to content

Commit

Permalink
feat: add SkipScheme() option (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 authored Nov 8, 2023
1 parent fef97ec commit 7c02dde
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 1 deletion.
28 changes: 28 additions & 0 deletions examples/skip_scheme/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"fmt"
"log"
"net/url"
"time"

"github.com/leg100/surl"
)

func main() {
signer := surl.New([]byte("secret_key"), surl.SkipScheme())

// Create a signed URL that expires in one hour.
signed, _ := signer.Sign("https://example.com/a/b/c?page=1", time.Hour)

// Change signed URL's scheme from https to http
u, _ := url.Parse(signed)
u.Scheme = "http"

// Verification should still succeed despite scheme having changed.
err := signer.Verify(u.String())
if err != nil {
log.Fatal("verification failed: ", err.Error())
}
fmt.Println("verification succeeded")
}
3 changes: 2 additions & 1 deletion formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ type formatter interface {

// payloadOptions are options that alter the payload to be signed.
type payloadOptions struct {
skipQuery bool
skipQuery bool
skipScheme bool
}
3 changes: 3 additions & 0 deletions path_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ func (f *pathFormatter) buildPayload(u url.URL, opts payloadOptions) string {
if opts.skipQuery {
u.RawQuery = ""
}
if opts.skipScheme {
u.Scheme = ""
}
return u.String()
}

Expand Down
3 changes: 3 additions & 0 deletions query_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ func (f *queryFormatter) buildPayload(u url.URL, opts payloadOptions) string {
expiry := u.Query().Get("expiry")
u.RawQuery = url.Values{"expiry": []string{expiry}}.Encode()
}
if opts.skipScheme {
u.Scheme = ""
}
return u.String()
}

Expand Down
8 changes: 8 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ surl.New(secret, surl.SkipQuery())

Skip the query string when computing the signature. This is useful, say, if you have pagination query parameters but you want to use the same signed URL regardless of their value. See the [example](./examples/skip_query/main.go).

#### Skip Scheme

```go
surl.New(secret, surl.SkipScheme())
```

Skip the scheme when computing the signature. This is useful, say, if you generate signed URLs in production where you use https but you want to use these URLs in development too where you use http. See the [example](./examples/skip_scheme/main.go).

#### Decimal Encoding of Expiry

```go
Expand Down
9 changes: 9 additions & 0 deletions signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ func SkipQuery() Option {
}
}

// SkipScheme instructs Signer to skip the scheme when computing the signature.
// This is useful, say, if you generate signed URLs in production where you use
// https but you want to use these URLs in development too where you use http.
func SkipScheme() Option {
return func(s *Signer) {
s.skipScheme = true
}
}

// PrefixPath prefixes the signed URL's path with a string. This can make it easier for a server
// to differentiate between signed and non-signed URLs. Note: the prefix is not
// part of the signature computation.
Expand Down
47 changes: 47 additions & 0 deletions signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,22 @@ var (
name: "skip query",
options: []Option{SkipQuery()},
},
{
name: "skip scheme",
options: []Option{SkipScheme()},
},
{
name: "prefix and skip query",
options: []Option{SkipQuery(), PrefixPath("/signed")},
},
{
name: "prefix and skip scheme",
options: []Option{SkipScheme(), PrefixPath("/signed")},
},
{
name: "prefix and skip query and skip scheme",
options: []Option{SkipQuery(), SkipScheme(), PrefixPath("/signed")},
},
}
)

Expand Down Expand Up @@ -148,6 +160,41 @@ func TestSigner_SkipQuery(t *testing.T) {
})
}

func TestSigner_SkipScheme(t *testing.T) {
// Demonstrate the SkipScheme option by changing the scheme on the signed
// URL and showing it still verifies.
t.Run("skip scheme", func(t *testing.T) {
signer := New([]byte("abc123"), SkipScheme())

unsigned := "https://example.com/a/b/c?foo=bar"
signed, err := signer.Sign(unsigned, time.Minute)
require.NoError(t, err)

u, err := url.Parse(signed)
require.NoError(t, err)
u.Scheme = "http"

err = signer.Verify(u.String())
require.NoError(t, err)
})

// Demonstrate how changing the scheme invalidates the signed URL
t.Run("do not skip scheme", func(t *testing.T) {
signer := New([]byte("abc123"))

unsigned := "https://example.com/a/b/c?foo=bar"
signed, err := signer.Sign(unsigned, time.Minute)
require.NoError(t, err)

u, err := url.Parse(signed)
require.NoError(t, err)
u.Scheme = "http"

err = signer.Verify(u.String())
assert.Equal(t, ErrInvalidSignature, err)
})
}

func TestSigner_Prefix(t *testing.T) {
signer := New([]byte("abc123"), PrefixPath("/signed"))

Expand Down

0 comments on commit 7c02dde

Please sign in to comment.