Skip to content

Commit

Permalink
WIP: Examples
Browse files Browse the repository at this point in the history
  • Loading branch information
kennyp committed Oct 21, 2024
1 parent 1b6a979 commit 491a139
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 25 deletions.
121 changes: 113 additions & 8 deletions dynoid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,23 @@ DynoID is a feature of [Heroku Private Spaces][spaces] that leverages [OpenID Co

All dynos are get an `heroku` audience token by default. Additional audiences can be minted by setting the `HEROKU_DYNO_ID_AUDIENCES` config var to a comma separated list of audiences.

## To Authenticate Calls to Your Service

The [dynoid](<#dynoid>) package provides all of the functions needed to verify a token issued for an app in an Heroku Space. Additionally there is a [middleware](<#middleware>) package that provides a set of http [handlers][handler] and middleware suitable for use in a web application.

### Direct Verification

In the case that you want to verify a token outside of an [http.Handler][handler] you can leverage the [Verifier](<#Verifier>) directly:


## Testing and Local Development

The [dynoidtest](<#dynoidtest>) package provides a number of functions useful for testing and local development.

[spaces]: https://devcenter.heroku.com/articles/private-spaces
[oidc]: https://openid.net/developers/how-connect-works/
[labs]: https://devcenter.heroku.com/categories/labs
[handler]: https://pkg.go.dev/net/http#Handler

<!-- gomarkdoc:embed:start -->

Expand Down Expand Up @@ -318,6 +331,58 @@ func (v *Verifier) Verify(ctx context.Context, rawIDToken string) (*Token, error

Verify validates the given token with the OIDC provider and validates it against the IssuerCallback

<details><summary>Example</summary>
<p>



```go
package main

import (
"fmt"

"github.com/heroku/x/dynoid"
"github.com/heroku/x/dynoid/internal"
)

const AUDIENCE = "testing"

func main() {
ctx, token := internal.GenerateToken(AUDIENCE)

verifier := dynoid.New(AUDIENCE)
verifier.IssuerCallback = func(issuer string) error {
if issuer != "https://oidc.heroku.local/spaces/test" {
return fmt.Errorf("unexpected issuer %q", issuer)
}

return nil
}

t, err := verifier.Verify(ctx, token)
if err != nil {
fmt.Printf("failed to verify token (%v)", err)
return
}

fmt.Println(t.Subject.AppID)
fmt.Println(t.Subject.AppName)
fmt.Println(t.Subject.Dyno)
}
```

#### Output

```
00000000-0000-0000-0000-000000000001
sushi
web.1
```

</p>
</details>

# dynoidtest

```go
Expand Down Expand Up @@ -348,6 +413,8 @@ dynoidtest provides helper functions for testing code that uses DynoID
- [func WithTokenOpts\(opts ...TokenOpt\) IssuerOpt](<#WithTokenOpts>)
- [type LocalConfiguration](<#LocalConfiguration>)
- [func ConfigureLocal\(audiences \[\]string, opts ...IssuerOpt\) \(\*LocalConfiguration, error\)](<#ConfigureLocal>)
- [func ConfigureLocalWithContext\(ctx context.Context, audiences \[\]string, opts ...IssuerOpt\) \(\*LocalConfiguration, error\)](<#ConfigureLocalWithContext>)
- [func \(c \*LocalConfiguration\) Context\(\) context.Context](<#LocalConfiguration.Context>)
- [func \(c \*LocalConfiguration\) GenerateToken\(audience string\) \(string, error\)](<#LocalConfiguration.GenerateToken>)
- [func \(c \*LocalConfiguration\) Handler\(\) http.Handler](<#LocalConfiguration.Handler>)
- [func \(c \*LocalConfiguration\) Middleware\(\) func\(http.Handler\) http.Handler](<#LocalConfiguration.Middleware>)
Expand All @@ -371,7 +438,7 @@ const (
```

<a name="GenerateDefaultFS"></a>
## func [GenerateDefaultFS](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L56>)
## func [GenerateDefaultFS](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L66>)

```go
func GenerateDefaultFS(iss *Issuer, audiences ...string) error
Expand All @@ -380,7 +447,7 @@ func GenerateDefaultFS(iss *Issuer, audiences ...string) error
Configure dynoid.DefaultFS to use tokens generated by the provided issuer for the audiences listed.

<a name="Issue"></a>
## func [Issue](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L86>)
## func [Issue](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L96>)

```go
func Issue(iss *Issuer) http.Handler
Expand All @@ -391,7 +458,7 @@ The Issue http.Handler generates test tokens via a local Issuer using the provid
A query param 'audience' is expected.

<a name="LocalIssuer"></a>
## func [LocalIssuer](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L73>)
## func [LocalIssuer](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L83>)

```go
func LocalIssuer(iss *Issuer) func(http.Handler) http.Handler
Expand Down Expand Up @@ -534,7 +601,7 @@ func WithTokenOpts(opts ...TokenOpt) IssuerOpt
WithTokenOpts allows a default set of TokenOpt to be applied to every token generated by the issuer

<a name="LocalConfiguration"></a>
## type [LocalConfiguration](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L15-L17>)
## type [LocalConfiguration](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L16-L19>)

LocalConfiguration provides methods for working with a local issuer configured with ConfigureLocal

Expand All @@ -545,7 +612,7 @@ type LocalConfiguration struct {
```

<a name="ConfigureLocal"></a>
### func [ConfigureLocal](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L41>)
### func [ConfigureLocal](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L25>)

```go
func ConfigureLocal(audiences []string, opts ...IssuerOpt) (*LocalConfiguration, error)
Expand All @@ -555,8 +622,26 @@ ConfigureLocal sets up the environment with a local DynoID issuer and generates

The returned LocalConfiguration provides methods for working with the issuer.

<a name="ConfigureLocalWithContext"></a>
### func [ConfigureLocalWithContext](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L29>)

```go
func ConfigureLocalWithContext(ctx context.Context, audiences []string, opts ...IssuerOpt) (*LocalConfiguration, error)
```



<a name="LocalConfiguration.Context"></a>
### func \(\*LocalConfiguration\) [Context](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L42>)

```go
func (c *LocalConfiguration) Context() context.Context
```



<a name="LocalConfiguration.GenerateToken"></a>
### func \(\*LocalConfiguration\) [GenerateToken](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L33>)
### func \(\*LocalConfiguration\) [GenerateToken](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L60>)

```go
func (c *LocalConfiguration) GenerateToken(audience string) (string, error)
Expand All @@ -565,7 +650,7 @@ func (c *LocalConfiguration) GenerateToken(audience string) (string, error)
GenerateToken mints a token for the given audience using the configured issuer.

<a name="LocalConfiguration.Handler"></a>
### func \(\*LocalConfiguration\) [Handler](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L27>)
### func \(\*LocalConfiguration\) [Handler](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L54>)

```go
func (c *LocalConfiguration) Handler() http.Handler
Expand All @@ -574,7 +659,7 @@ func (c *LocalConfiguration) Handler() http.Handler
Handler mints tokens for the configured issuer using for the audience specified by the audience query param.

<a name="LocalConfiguration.Middleware"></a>
### func \(\*LocalConfiguration\) [Middleware](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L21>)
### func \(\*LocalConfiguration\) [Middleware](<https://github.com/heroku/x/blob/master/dynoid/dynoidtest/local.go#L48>)

```go
func (c *LocalConfiguration) Middleware() func(http.Handler) http.Handler
Expand Down Expand Up @@ -611,6 +696,26 @@ func WithSubjectFunc(fn func(audience string, subject *dynoid.Subject) *dynoid.S

WithSubjectFunc allows the Subject to be different than the default based on the audience being generated.

# internal

```go
import "github.com/heroku/x/dynoid/internal"
```

## Index

- [func GenerateToken\(audience string\) \(context.Context, string\)](<#GenerateToken>)


<a name="GenerateToken"></a>
## func [GenerateToken](<https://github.com/heroku/x/blob/master/dynoid/internal/example_helpers.go#L21>)

```go
func GenerateToken(audience string) (context.Context, string)
```



# middleware

```go
Expand Down
44 changes: 27 additions & 17 deletions dynoid/dynoidtest/local.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dynoidtest

import (
"context"
"fmt"
"log/slog"
"net/http"
Expand All @@ -13,9 +14,35 @@ import (
// LocalConfiguration provides methods for working with a local issuer
// configured with ConfigureLocal
type LocalConfiguration struct {
ctx context.Context
iss *Issuer
}

// ConfigureLocal sets up the environment with a local DynoID issuer and
// generates tokens for the audiences provided.
//
// The returned LocalConfiguration provides methods for working with the issuer.
func ConfigureLocal(audiences []string, opts ...IssuerOpt) (*LocalConfiguration, error) {
return ConfigureLocalWithContext(context.Background(), audiences, opts...)
}

func ConfigureLocalWithContext(ctx context.Context, audiences []string, opts ...IssuerOpt) (*LocalConfiguration, error) {
ctx, iss, err := NewWithContext(ctx, opts...)
if err != nil {
return nil, fmt.Errorf("failed to create issuer (%w)", err)
}

if err := GenerateDefaultFS(iss, audiences...); err != nil {
return nil, fmt.Errorf("failed to generate DefaultFS (%w)", err)
}

return &LocalConfiguration{ctx, iss}, nil
}

func (c *LocalConfiguration) Context() context.Context {
return c.ctx
}

// The Middleware should be inserted in the middleware stack before any
// functions that use dynoid are called.
func (c *LocalConfiguration) Middleware() func(http.Handler) http.Handler {
Expand All @@ -34,23 +61,6 @@ func (c *LocalConfiguration) GenerateToken(audience string) (string, error) {
return c.iss.GenerateIDToken(audience)
}

// ConfigureLocal sets up the environment with a local DynoID issuer and
// generates tokens for the audiences provided.
//
// The returned LocalConfiguration provides methods for working with the issuer.
func ConfigureLocal(audiences []string, opts ...IssuerOpt) (*LocalConfiguration, error) {
iss, err := New(opts...)
if err != nil {
return nil, fmt.Errorf("failed to create issuer (%w)", err)
}

if err := GenerateDefaultFS(iss, audiences...); err != nil {
return nil, fmt.Errorf("failed to generate DefaultFS (%w)", err)
}

return &LocalConfiguration{iss}, nil
}

// Configure dynoid.DefaultFS to use tokens generated by the provided issuer
// for the audiences listed.
func GenerateDefaultFS(iss *Issuer, audiences ...string) error {
Expand Down
37 changes: 37 additions & 0 deletions dynoid/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package dynoid_test

import (
"fmt"

"github.com/heroku/x/dynoid"
"github.com/heroku/x/dynoid/internal"
)

const AUDIENCE = "testing"

func ExampleVerifier_Verify() {
ctx, token := internal.GenerateToken(AUDIENCE)

verifier := dynoid.New(AUDIENCE)
verifier.IssuerCallback = func(issuer string) error {
if issuer != "https://oidc.heroku.local/spaces/test" {
return fmt.Errorf("unexpected issuer %q", issuer)
}

return nil
}

t, err := verifier.Verify(ctx, token)
if err != nil {
fmt.Printf("failed to verify token (%v)", err)
return
}

fmt.Println(t.Subject.AppID)
fmt.Println(t.Subject.AppName)
fmt.Println(t.Subject.Dyno)
// Output:
// 00000000-0000-0000-0000-000000000001
// sushi
// web.1
}
28 changes: 28 additions & 0 deletions dynoid/internal/example_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package internal

import (
"context"
"sync"

"github.com/heroku/x/dynoid/dynoidtest"
)

var (
cfg = sync.OnceValue(func() *dynoidtest.LocalConfiguration {
cfg, err := dynoidtest.ConfigureLocal([]string{})
if err != nil {
panic(err)
}

return cfg
})
)

func GenerateToken(audience string) (context.Context, string) {
token, err := cfg().GenerateToken(audience)
if err != nil {
panic(err)
}

return cfg().Context(), token
}

0 comments on commit 491a139

Please sign in to comment.