File: example_test.go

package info (click to toggle)
golang-github-nats-io-jwt 2.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 680 kB
  • sloc: makefile: 30; sh: 26
file content (199 lines) | stat: -rw-r--r-- 5,017 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package jwt

import (
	"fmt"
	"os"
	"path"
	"testing"

	jwt "github.com/nats-io/jwt/v2/v1compat"
	"github.com/nats-io/nkeys"
)

func TestExample(t *testing.T) {
	// create an operator key pair (private key)
	okp, err := nkeys.CreateOperator()
	if err != nil {
		t.Fatal(err)
	}
	// extract the public key
	opk, err := okp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}

	// create an operator claim using the public key for the identifier
	oc := jwt.NewOperatorClaims(opk)
	oc.Name = "O"
	// add an operator signing key to sign accounts
	oskp, err := nkeys.CreateOperator()
	if err != nil {
		t.Fatal(err)
	}
	// get the public key for the signing key
	ospk, err := oskp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}
	// add the signing key to the operator - this makes any account
	// issued by the signing key to be valid for the operator
	oc.SigningKeys.Add(ospk)

	// self-sign the operator JWT - the operator trusts itself
	operatorJWT, err := oc.Encode(okp)
	if err != nil {
		t.Fatal(err)
	}

	// create an account keypair
	akp, err := nkeys.CreateAccount()
	if err != nil {
		t.Fatal(err)
	}
	// extract the public key for the account
	apk, err := akp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}
	// create the claim for the account using the public key of the account
	ac := jwt.NewAccountClaims(apk)
	ac.Name = "A"
	// create a signing key that we can use for issuing users
	askp, err := nkeys.CreateAccount()
	if err != nil {
		t.Fatal(err)
	}
	// extract the public key
	aspk, err := askp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}
	// add the signing key (public) to the account
	ac.SigningKeys.Add(aspk)

	// now we could encode an issue the account using the operator
	// key that we generated above, but this will illustrate that
	// the account could be self-signed, and given to the operator
	// who can then re-sign it
	accountJWT, err := ac.Encode(akp)
	if err != nil {
		t.Fatal(err)
	}

	// the operator would decode the provided token, if the token
	// is not self-signed or signed by an operator or tampered with
	// the decoding would fail
	ac, err = jwt.DecodeAccountClaims(accountJWT)
	if err != nil {
		t.Fatal(err)
	}
	// here the operator is going to use its private signing key to
	// re-issue the account
	accountJWT, err = ac.Encode(oskp)
	if err != nil {
		t.Fatal(err)
	}

	// now back to the account, the account can issue users
	// need not be known to the operator - the users are trusted
	// because they will be signed by the account. The server will
	// look up the account get a list of keys the account has and
	// verify that the user was issued by one of those keys
	ukp, err := nkeys.CreateUser()
	if err != nil {
		t.Fatal(err)
	}
	upk, err := ukp.PublicKey()
	if err != nil {
		t.Fatal(err)
	}
	uc := jwt.NewUserClaims(upk)
	// since the jwt will be issued by a signing key, the issuer account
	// must be set to the public ID of the account
	uc.IssuerAccount = apk
	userJwt, err := uc.Encode(askp)
	if err != nil {
		t.Fatal(err)
	}
	// the seed is a version of the keypair that is stored as text
	useed, err := ukp.Seed()
	if err != nil {
		t.Fatal(err)
	}
	// generate a creds formatted file that can be used by a NATS client
	creds, err := jwt.FormatUserConfig(userJwt, useed)
	if err != nil {
		t.Fatal(err)
	}

	// now we are going to put it together into something that can be run
	// we create a directory to store the server configuration, the creds
	// file and a small go program that uses the creds file
	dir, err := os.MkdirTemp(os.TempDir(), "jwt_example")
	if err != nil {
		t.Fatal(err)
	}
	// print where we generated the file
	t.Logf("generated example %s", dir)
	t.Log("to run this example:")
	t.Logf("> cd %s", dir)
	t.Log("> go mod init example")
	t.Log("> go mod tidy")
	t.Logf("> nats-server -c %s/resolver.conf &", dir)
	t.Log("> go run main.go")

	// we are generating a memory resolver server configuration
	// it lists the operator and all account jwts the server should
	// know about
	resolver := fmt.Sprintf(`operator: %s

resolver: MEMORY
resolver_preload: {
	%s: %s
}
`, operatorJWT, apk, accountJWT)
	if err := os.WriteFile(path.Join(dir, "resolver.conf"),
		[]byte(resolver), 0644); err != nil {
		t.Fatal(err)
	}

	// store the creds
	credsPath := path.Join(dir, "u.creds")
	if err := os.WriteFile(credsPath, creds, 0644); err != nil {
		t.Fatal(err)
	}

	// here we generate as small go program that connects using the creds file
	// subscribes, and publishes a message
	connect := fmt.Sprintf(`
package main

import (
  "fmt"
  "sync"

  "github.com/nats-io/nats.go"
)

func main() {
  var wg sync.WaitGroup
  wg.Add(1)
  nc, err := nats.Connect(nats.DefaultURL, nats.UserCredentials(%q))
  if err != nil {
	panic(err)
  }
  nc.Subscribe("hello.world", func(m *nats.Msg) {
    fmt.Println(m.Subject)
	wg.Done()
  })
  nc.Publish("hello.world", []byte("hello"))
  nc.Flush()
  wg.Wait()
  nc.Close()
}

`, credsPath)
	if err := os.WriteFile(path.Join(dir, "main.go"), []byte(connect), 0644); err != nil {
		t.Fatal(err)
	}
}