github.com/nats-io/jwt/v2@v2.5.6/example_test.go (about)

     1  package jwt
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"testing"
     8  
     9  	jwt "github.com/nats-io/jwt/v2/v1compat"
    10  	"github.com/nats-io/nkeys"
    11  )
    12  
    13  func TestExample(t *testing.T) {
    14  	// create an operator key pair (private key)
    15  	okp, err := nkeys.CreateOperator()
    16  	if err != nil {
    17  		t.Fatal(err)
    18  	}
    19  	// extract the public key
    20  	opk, err := okp.PublicKey()
    21  	if err != nil {
    22  		t.Fatal(err)
    23  	}
    24  
    25  	// create an operator claim using the public key for the identifier
    26  	oc := jwt.NewOperatorClaims(opk)
    27  	oc.Name = "O"
    28  	// add an operator signing key to sign accounts
    29  	oskp, err := nkeys.CreateOperator()
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  	// get the public key for the signing key
    34  	ospk, err := oskp.PublicKey()
    35  	if err != nil {
    36  		t.Fatal(err)
    37  	}
    38  	// add the signing key to the operator - this makes any account
    39  	// issued by the signing key to be valid for the operator
    40  	oc.SigningKeys.Add(ospk)
    41  
    42  	// self-sign the operator JWT - the operator trusts itself
    43  	operatorJWT, err := oc.Encode(okp)
    44  	if err != nil {
    45  		t.Fatal(err)
    46  	}
    47  
    48  	// create an account keypair
    49  	akp, err := nkeys.CreateAccount()
    50  	if err != nil {
    51  		t.Fatal(err)
    52  	}
    53  	// extract the public key for the account
    54  	apk, err := akp.PublicKey()
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	// create the claim for the account using the public key of the account
    59  	ac := jwt.NewAccountClaims(apk)
    60  	ac.Name = "A"
    61  	// create a signing key that we can use for issuing users
    62  	askp, err := nkeys.CreateAccount()
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  	// extract the public key
    67  	aspk, err := askp.PublicKey()
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  	// add the signing key (public) to the account
    72  	ac.SigningKeys.Add(aspk)
    73  
    74  	// now we could encode an issue the account using the operator
    75  	// key that we generated above, but this will illustrate that
    76  	// the account could be self-signed, and given to the operator
    77  	// who can then re-sign it
    78  	accountJWT, err := ac.Encode(akp)
    79  	if err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	// the operator would decode the provided token, if the token
    84  	// is not self-signed or signed by an operator or tampered with
    85  	// the decoding would fail
    86  	ac, err = jwt.DecodeAccountClaims(accountJWT)
    87  	if err != nil {
    88  		t.Fatal(err)
    89  	}
    90  	// here the operator is going to use its private signing key to
    91  	// re-issue the account
    92  	accountJWT, err = ac.Encode(oskp)
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	// now back to the account, the account can issue users
    98  	// need not be known to the operator - the users are trusted
    99  	// because they will be signed by the account. The server will
   100  	// look up the account get a list of keys the account has and
   101  	// verify that the user was issued by one of those keys
   102  	ukp, err := nkeys.CreateUser()
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	upk, err := ukp.PublicKey()
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  	uc := jwt.NewUserClaims(upk)
   111  	// since the jwt will be issued by a signing key, the issuer account
   112  	// must be set to the public ID of the account
   113  	uc.IssuerAccount = apk
   114  	userJwt, err := uc.Encode(askp)
   115  	if err != nil {
   116  		t.Fatal(err)
   117  	}
   118  	// the seed is a version of the keypair that is stored as text
   119  	useed, err := ukp.Seed()
   120  	if err != nil {
   121  		t.Fatal(err)
   122  	}
   123  	// generate a creds formatted file that can be used by a NATS client
   124  	creds, err := jwt.FormatUserConfig(userJwt, useed)
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  
   129  	// now we are going to put it together into something that can be run
   130  	// we create a directory to store the server configuration, the creds
   131  	// file and a small go program that uses the creds file
   132  	dir, err := os.MkdirTemp(os.TempDir(), "jwt_example")
   133  	if err != nil {
   134  		t.Fatal(err)
   135  	}
   136  	// print where we generated the file
   137  	t.Logf("generated example %s", dir)
   138  	t.Log("to run this example:")
   139  	t.Logf("> cd %s", dir)
   140  	t.Log("> go mod init example")
   141  	t.Log("> go mod tidy")
   142  	t.Logf("> nats-server -c %s/resolver.conf &", dir)
   143  	t.Log("> go run main.go")
   144  
   145  	// we are generating a memory resolver server configuration
   146  	// it lists the operator and all account jwts the server should
   147  	// know about
   148  	resolver := fmt.Sprintf(`operator: %s
   149  
   150  resolver: MEMORY
   151  resolver_preload: {
   152  	%s: %s
   153  }
   154  `, operatorJWT, apk, accountJWT)
   155  	if err := os.WriteFile(path.Join(dir, "resolver.conf"),
   156  		[]byte(resolver), 0644); err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	// store the creds
   161  	credsPath := path.Join(dir, "u.creds")
   162  	if err := os.WriteFile(credsPath, creds, 0644); err != nil {
   163  		t.Fatal(err)
   164  	}
   165  
   166  	// here we generate as small go program that connects using the creds file
   167  	// subscribes, and publishes a message
   168  	connect := fmt.Sprintf(`
   169  package main
   170  
   171  import (
   172    "fmt"
   173    "sync"
   174  
   175    "github.com/nats-io/nats.go"
   176  )
   177  
   178  func main() {
   179    var wg sync.WaitGroup
   180    wg.Add(1)
   181    nc, err := nats.Connect(nats.DefaultURL, nats.UserCredentials(%q))
   182    if err != nil {
   183  	panic(err)
   184    }
   185    nc.Subscribe("hello.world", func(m *nats.Msg) {
   186      fmt.Println(m.Subject)
   187  	wg.Done()
   188    })
   189    nc.Publish("hello.world", []byte("hello"))
   190    nc.Flush()
   191    wg.Wait()
   192    nc.Close()
   193  }
   194  
   195  `, credsPath)
   196  	if err := os.WriteFile(path.Join(dir, "main.go"), []byte(connect), 0644); err != nil {
   197  		t.Fatal(err)
   198  	}
   199  }