diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..0baf015 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +/_site diff --git a/docs/api/.gitignore b/docs/api/.gitignore new file mode 100644 index 0000000..e8079a3 --- /dev/null +++ b/docs/api/.gitignore @@ -0,0 +1,5 @@ +############### +# temp file # +############### +*.yml +.manifest diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 0000000..daef585 --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,5 @@ +# API Documentation + +You can browse the latest jwt.net API Documentation[^1] [here](NATS.Jwt.yml). + +[^1]: API Documentation is auto-generated using [DocFX](https://dotnet.github.io/docfx/). diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..8584ef8 --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,65 @@ +{ + "metadata": [ + { + "src": [ + { + "files": [ "**/*.csproj" ], + "src": "../src" + } + ], + "dest": "api", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "nested", + "memberLayout": "separatePages", + "allowCompilationErrors": false + } + ], + "build": { + "content": [ + { + "files": [ + "api/**.yml", + "api/index.md" + ] + }, + { + "files": [ + "documentation/**.md", + "documentation/**/toc.yml", + "toc.yml", + "*.md" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "output": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ + "default", + "modern", + "nats_template" + ], + "postProcessors": [], + "keepFileLink": false, + "disableGitFeatures": false, + "markdownEngineName": "markdig", + "markdownEngineProperties": { + "markdigExtensions": [ + "abbreviations", + "definitionlists", + "tasklists", + "footnotes" + ] + } + } +} diff --git a/docs/documentation/intro.md b/docs/documentation/intro.md new file mode 100644 index 0000000..f82c528 --- /dev/null +++ b/docs/documentation/intro.md @@ -0,0 +1,26 @@ +# Introduction + +This is a .NET implementation of the JWT library for the NATS ecosystem. + +A [JWT](https://jwt.io/) implementation that uses [nkeys](https://github.com/nats-io/nkeys.net) to digitally sign +JWT tokens for the [NATS](https://nats.io/) ecosystem. + +See also https://github.com/nats-io/jwt + +## Installation + +Reference [Jwt.net NuGet package](https://www.nuget.org/packages/jwt.net) in your project: +You can install the package via NuGet: + +```bash +dotnet add package NATS.Jwt --prerelease +``` + +## Usage + +[!code-csharp[](../../tests/NATS.Jwt.DocsExamples/IntroPage.cs#nats-jwt)] + + +## What's Next + +*Documentation is in progress.* Help us improve the documentation by contributing today! diff --git a/docs/documentation/toc.yml b/docs/documentation/toc.yml new file mode 100644 index 0000000..51806d4 --- /dev/null +++ b/docs/documentation/toc.yml @@ -0,0 +1,5 @@ +- name: Introduction + href: intro.md + +- name: Updating Documentation + href: update-docs.md diff --git a/docs/documentation/update-docs.md b/docs/documentation/update-docs.md new file mode 100644 index 0000000..49c1d7e --- /dev/null +++ b/docs/documentation/update-docs.md @@ -0,0 +1,24 @@ +# Updating Documentation + +As well as being able to edit pages on GitHub, you can also edit and update this documentation, +view locally and submit a Pull Request to be included in this documentation site. + +## Running DocFX locally + +You mush have [DocFX installed](https://dotnet.github.io/docfx/): +Clone the jwt.net ([nats-io/jwt.net](https://github.com/nats-io/jwt.net)) repository, then `cd docs` and run local server (`docfx --serve`) to view this documentation site. + +``` +dotnet tool update -g docfx +``` + +Generate API documentation and run local server: +``` +git clone https://github.com/nats-io/jwt.net.git +cd jwt.net/docs +docfx --serve +``` + +You might not be a .NET developer, but still want to contribute to the documentation. +You can install the .NET SDK and use the `docfx` tool to generate the documentation. +Download the .NET SDK from [dot.net](https://dot.net). diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..0943bde --- /dev/null +++ b/docs/index.md @@ -0,0 +1,26 @@ +# Introduction + +This is a .NET implementation of the JWT library for the NATS ecosystem. + +A [JWT](https://jwt.io/) implementation that uses [nkeys](https://github.com/nats-io/nkeys.net) to digitally sign +JWT tokens for the [NATS](https://nats.io/) ecosystem. + +See also https://github.com/nats-io/jwt + +## Installation + +Reference [Jwt.net NuGet package](https://www.nuget.org/packages/jwt.net) in your project: +You can install the package via NuGet: + +```bash +dotnet add package NATS.Jwt --prerelease +``` + +## Usage + +[!code-csharp[](../tests/NATS.Jwt.DocsExamples/IntroPage.cs#nats-jwt)] + + +## What's Next + +*Documentation is in progress.* Help us improve the documentation by contributing today! diff --git a/docs/nats_template/favicon.ico b/docs/nats_template/favicon.ico new file mode 100644 index 0000000..9464855 Binary files /dev/null and b/docs/nats_template/favicon.ico differ diff --git a/docs/nats_template/logo.svg b/docs/nats_template/logo.svg new file mode 100644 index 0000000..950d0a2 --- /dev/null +++ b/docs/nats_template/logo.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..e3639aa --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,12 @@ +- name: Documentation + href: documentation/ + +- name: API + href: api/ + homepage: api/index.md + +- name: GitHub + href: https://github.com/nats-io/jwt.net + +- name: Slack + href: https://slack.nats.io diff --git a/src/NATS.Jwt.sln b/src/NATS.Jwt.sln index fa7803f..a674182 100644 --- a/src/NATS.Jwt.sln +++ b/src/NATS.Jwt.sln @@ -42,6 +42,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\test.yml = ..\.github\workflows\test.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Jwt.DocsExamples", "..\tests\NATS.Jwt.DocsExamples\NATS.Jwt.DocsExamples.csproj", "{F6EC8BE3-17D7-4857-81B3-C3DAAF9DEF4F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +65,10 @@ Global {E144211D-3549-4E78-BFC4-B5BF7E70AEDA}.Debug|Any CPU.Build.0 = Debug|Any CPU {E144211D-3549-4E78-BFC4-B5BF7E70AEDA}.Release|Any CPU.ActiveCfg = Release|Any CPU {E144211D-3549-4E78-BFC4-B5BF7E70AEDA}.Release|Any CPU.Build.0 = Release|Any CPU + {F6EC8BE3-17D7-4857-81B3-C3DAAF9DEF4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F6EC8BE3-17D7-4857-81B3-C3DAAF9DEF4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F6EC8BE3-17D7-4857-81B3-C3DAAF9DEF4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F6EC8BE3-17D7-4857-81B3-C3DAAF9DEF4F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {9B613C47-922B-4AA8-82D7-1520A13E0F74} = {F3E7EFAD-B3EA-45F4-907B-F3C2338771CE} @@ -71,5 +77,6 @@ Global {1EB01F81-8857-4646-B8BF-16024F357506} = {03E5999C-5D20-4EA2-B8C8-53ACE65B221F} {9C98B59B-D0C0-4F2B-8333-2242ED1B5F70} = {03E5999C-5D20-4EA2-B8C8-53ACE65B221F} {03E5999C-5D20-4EA2-B8C8-53ACE65B221F} = {1F24478C-D5CB-4A58-A74E-6371F7F95C01} + {F6EC8BE3-17D7-4857-81B3-C3DAAF9DEF4F} = {79B0CD3B-C106-44A2-9A2A-CFDA69A3016A} EndGlobalSection EndGlobal diff --git a/tests/NATS.Jwt.DocsExamples/IntroPage.cs b/tests/NATS.Jwt.DocsExamples/IntroPage.cs new file mode 100644 index 0000000..f11de5c --- /dev/null +++ b/tests/NATS.Jwt.DocsExamples/IntroPage.cs @@ -0,0 +1,101 @@ +using NATS.Client.Core; +using NATS.NKeys; + +namespace NATS.Jwt.DocsExamples; + +public class IntroPage +{ + public static async Task Run() + { + Console.WriteLine("____________________________________________________________"); + Console.WriteLine("NATS.Jwt.DocsExamples.IntroPage"); + + { + #region nats-jwt + + var jwt = new NatsJwt(); + + // create an operator key pair (private key) + var okp = KeyPair.CreatePair(PrefixByte.Operator); + var opk = okp.GetPublicKey(); + + // create an operator claim using the public key for the identifier + var oc = jwt.NewOperatorClaims(opk); + oc.Name = "Example Operator"; + + // add an operator signing key to sign accounts + var oskp = KeyPair.CreatePair(PrefixByte.Operator); + var ospk = oskp.GetPublicKey(); + + // add the signing key to the operator - this makes any account + // issued by the signing key to be valid for the operator + oc.Operator.SigningKeys = [ospk]; + + // self-sign the operator JWT - the operator trusts itself + var operatorJwt = jwt.EncodeOperatorClaims(oc, okp); + + // create an account keypair + var akp = KeyPair.CreatePair(PrefixByte.Account); + var apk = akp.GetPublicKey(); + + // create the claim for the account using the public key of the account + var ac = jwt.NewAccountClaims(apk); + ac.Name = "Example Account"; + + var askp = KeyPair.CreatePair(PrefixByte.Account); + var aspk = askp.GetPublicKey(); + + // add the signing key (public) to the account + ac.Account.SigningKeys = [aspk]; + var accountJwt = jwt.EncodeAccountClaims(ac, oskp); + + // 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 + var ukp = KeyPair.CreatePair(PrefixByte.User); + var upk = ukp.GetPublicKey(); + var 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.User.IssuerAccount = apk; + var userJwt = jwt.EncodeUserClaims(uc, askp); + + // the seed is a version of the keypair that is stored as text + var userSeed = ukp.GetSeed(); + + var conf = $$""" + operator: {{operatorJwt}} + + resolver: MEMORY + resolver_preload: { + {{apk}}: {{accountJwt}} + } + """; + + // generate a creds formatted file that can be used by a NATS client + const string credsPath = $"example_user.creds"; + await File.WriteAllTextAsync(credsPath, jwt.FormatUserConfig(userJwt, userSeed)); + + // now we are going to put it together into something that can be run + // we create a file to store the server configuration, the creds + // file and a small program that uses the creds file + const string confPath = $"example_server.conf"; + await File.WriteAllTextAsync(confPath, conf); + + // run the server: + // > nats-server -c example_server.conf + + // Connect as user + var serverUrl = "nats://localhost:4222"; + var authOpts = new NatsAuthOpts { CredsFile = credsPath }; + var opts = new NatsOpts { Url = serverUrl, AuthOpts = authOpts }; + await using var nats = new NatsConnection(opts); + await nats.PingAsync(); + + #endregion + } + } +} diff --git a/tests/NATS.Jwt.DocsExamples/NATS.Jwt.DocsExamples.csproj b/tests/NATS.Jwt.DocsExamples/NATS.Jwt.DocsExamples.csproj new file mode 100644 index 0000000..d5c22c6 --- /dev/null +++ b/tests/NATS.Jwt.DocsExamples/NATS.Jwt.DocsExamples.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + diff --git a/tests/NATS.Jwt.DocsExamples/Program.cs b/tests/NATS.Jwt.DocsExamples/Program.cs new file mode 100644 index 0000000..6c1e0a8 --- /dev/null +++ b/tests/NATS.Jwt.DocsExamples/Program.cs @@ -0,0 +1,2 @@ + +await NATS.Jwt.DocsExamples.IntroPage.Run();