Initial commit
This commit is contained in:
Binary file not shown.
+296
@@ -0,0 +1,296 @@
|
||||
<p align="center">
|
||||
<img width="300" src=".github/supabase-gotrue.png"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/supabase/gotrue-csharp/workflows/Build%20And%20Test/badge.svg"/>
|
||||
<a href="https://www.nuget.org/packages/gotrue-csharp/">
|
||||
<img src="https://img.shields.io/badge/dynamic/json?color=green&label=Nuget%20Release&query=data[0].version&url=https%3A%2F%2Fazuresearch-usnc.nuget.org%2Fquery%3Fq%3Dpackageid%3Agotrue-csharp"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## New Features
|
||||
|
||||
### Unity Support
|
||||
|
||||
The Client works with Unity. You can find an example of a session persistence
|
||||
implementation for Unity at this [gist](https://gist.github.com/wiverson/fbb07498743dff19b72c9c58599931e9).
|
||||
|
||||
```csharp
|
||||
|
||||
```
|
||||
|
||||
### Offline Support
|
||||
|
||||
The Client now better supports online/offline usage. The Client now has a simple boolean option "Online"
|
||||
which can be set to to false. This can be combined with the NetworkStatus class to allow the client
|
||||
to automatically go online & offline based on the device's network status.
|
||||
|
||||
To use this new NetworkStatus, add the following:
|
||||
|
||||
```csharp
|
||||
// Create the client
|
||||
var client = new Client(new ClientOptions { AllowUnconfirmedUserSessions = true });
|
||||
// Create the network status monitor
|
||||
var status = new NetworkStatus();
|
||||
// Tell the network status monitor to update the client's online status
|
||||
status.Client = client;
|
||||
// Start the network status monitor
|
||||
await status.StartAsync();
|
||||
// rest of the usual client configuration
|
||||
```
|
||||
|
||||
Only the stateful Client supports this feature, and only for the managed user sessions.
|
||||
Admin JWT methods and the stateless client are not affected.
|
||||
|
||||
By default, this change will not affect existing code.
|
||||
|
||||
### Updated Refresh Token Handling
|
||||
|
||||
The Client now supports setting a maximum wait time before refreshing the token. This is useful
|
||||
for scenarios where you want to refresh the token before it expires, but not too often.
|
||||
|
||||
By default, GoTrue servers are typically set to expire the token after an hour, and the refresh
|
||||
thread will refresh the token when ~20% of that time is left.
|
||||
|
||||
However, you can set the expiration time to be much longer on the server (up to a week). In this
|
||||
scenario, you may want to refresh the token more often than once every 5 days or so, but not every hour.
|
||||
|
||||
There is now a new option `MaximumRefreshWaitTime` which allows you to specify the maximum amount
|
||||
in time that the refresh thread will wait before refreshing the token. This defaults to 4 hours.
|
||||
This means that if you have your server set to a one hour token expiration, nothing changes, but
|
||||
if you extend the server refresh to (for example) a week, as long as the user launches the app
|
||||
at least once a week, they will never have to re-authenticate.
|
||||
|
||||
## BREAKING CHANGES v3.1 → v4.x
|
||||
|
||||
- Exceptions have been simplified to a single `GotrueException`. A `Reason` field has been added
|
||||
to `GotrueException` to clarify what happened. This should also be easier to manage as the Gotrue
|
||||
server API & messages evolve.
|
||||
- The session delegates for `Save`/`Load`/`Destroy` have been simplified to no longer require `async`.
|
||||
- Console logging in a few places (most notable the background refresh thread) has been removed
|
||||
in favor of a notification method. See `Client.AddDebugListener()` and the test cases for examples.
|
||||
This will allow you to implement your own logging strategy (write to temp file, console, user visible
|
||||
err console, etc).
|
||||
- The client now more reliably emits AuthState changes.
|
||||
- There is now a single source of truth for headers in the stateful Client - the `Options` headers.
|
||||
|
||||
New feature:
|
||||
|
||||
- Added a `Settings` request to the stateless API only - you can now query the server instance to
|
||||
determine if it's got the settings you need. This might allow for things like a visual
|
||||
component in a tool to verify the GoTrue settings are working correctly, or tests that run differently
|
||||
depending on the server configuration.
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- Test cases have been added to help ensure reliability of auth state change notifications
|
||||
and persistence.
|
||||
- Persistence is now managed via the same notifications as auth state change
|
||||
|
||||
## BREAKING CHANGES v3.0 → 3.1
|
||||
|
||||
- We've implemented the PKCE auth flow. SignIn using a provider now returns an instance of `ProviderAuthState` rather
|
||||
than a `string`.
|
||||
- The provider sign in signature has moved `scopes` into `SignInOptions`
|
||||
|
||||
In Short:
|
||||
|
||||
```c#
|
||||
# What was:
|
||||
var url = await client.SignIn(Provider.Github, "scopes and things");
|
||||
|
||||
# Becomes:
|
||||
var state = await client.SignIn(Provider.Github, new SignInOptions { "scopes and things" });
|
||||
// Url is now at `state.Uri`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
To use this library on the Supabase Hosted service but separately from the `supabase-csharp`, you'll need to specify
|
||||
your url and public key like so:
|
||||
|
||||
```c#
|
||||
var auth = new Supabase.Gotrue.Client(new ClientOptions<Session>
|
||||
{
|
||||
Url = "https://PROJECT_ID.supabase.co/auth/v1",
|
||||
Headers = new Dictionary<string, string>
|
||||
{
|
||||
{ "apikey", SUPABASE_PUBLIC_KEY }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Otherwise, using it this library with a local instance:
|
||||
|
||||
```c#
|
||||
var options = new ClientOptions { Url = "https://example.com/api" };
|
||||
var client = new Client(options);
|
||||
var user = await client.SignUp("new-user@example.com");
|
||||
|
||||
// Alternatively, you can use a StatelessClient and do API interactions that way
|
||||
var options = new StatelessClientOptions { Url = "https://example.com/api" }
|
||||
await new StatelessClient().SignUp("new-user@example.com", options);
|
||||
```
|
||||
|
||||
## Persisting, Retrieving, and Destroying Sessions.
|
||||
|
||||
This Gotrue client is written to be agnostic when it comes to session persistence, retrieval, and
|
||||
destruction. `ClientOptions` exposes
|
||||
properties that allow these to be specified.
|
||||
|
||||
In the event these are specified and the `AutoRefreshToken` option is set, as the `Client` Initializes, it will also
|
||||
attempt to
|
||||
retrieve, set, and refresh an existing session.
|
||||
|
||||
For example, using `Xamarin.Essentials` in `Xamarin.Forms`, this might look like:
|
||||
|
||||
```c#
|
||||
// This is a method you add your application launch/setup
|
||||
async void Initialize() {
|
||||
|
||||
// Specify the methods you'd like to use as persistence callbacks
|
||||
var persistence = new GotrueSessionPersistence(SaveSession, LoadSession, DestroySession);
|
||||
var client = new Client(
|
||||
Url = GOTRUE_URL,
|
||||
new ClientOptions {
|
||||
AllowUnconfirmedUserSessions = true,
|
||||
SessionPersistence = persistence });
|
||||
|
||||
// Specify a debug callback to listen to problems with the background token refresh thread
|
||||
client.AddDebugListener(LogDebug);
|
||||
|
||||
// Specify a call back to listen to changes in the user state (logged in, out, etc)
|
||||
client.AddStateChangedListener(AuthStateListener);
|
||||
|
||||
// Load the session from persistence
|
||||
client.LoadSession();
|
||||
// Loads the session using SessionRetriever and sets state internally.
|
||||
await client.RetrieveSessionAsync();
|
||||
}
|
||||
|
||||
// Add callback methods for above
|
||||
// Here's a quick example of using this to save session data to the user's cache folder
|
||||
// You'll want to add methods for loading the file and deleting when the user logs out
|
||||
internal bool SaveSession(Session session)
|
||||
{
|
||||
var cacheFileName = ".gotrue.cache";
|
||||
|
||||
try
|
||||
{
|
||||
var cacheDir = FileSystem.CacheDirectory;
|
||||
var path = Path.Join(cacheDir, cacheFileName);
|
||||
var str = JsonConvert.SerializeObject(session);
|
||||
|
||||
using (StreamWriter file = new StreamWriter(path))
|
||||
{
|
||||
file.Write(str);
|
||||
file.Dispose();
|
||||
return Task.FromResult(true);
|
||||
};
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
Debug.WriteLine("Unable to write cache file.");
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3rd Party OAuth
|
||||
|
||||
Once again, Gotrue client is written to be agnostic of platform. In order for Gotrue to sign in a user from an Oauth
|
||||
callback, the PKCE flow is preferred:
|
||||
|
||||
1) The Callback Url must be set in the Supabase Admin panel
|
||||
2) The Application should have listener to receive that Callback
|
||||
3) Generate a sign in request using: `client.SignIn(PROVIDER, options)` and setting the options to use the
|
||||
PKCE `FlowType`
|
||||
4) Store `ProviderAuthState.PKCEVerifier` so that the application callback can use it to verify the returned code
|
||||
5) In the Callback, use stored `PKCEVerifier` and received `code` to exchange for a session.
|
||||
|
||||
```c#
|
||||
var state = await client.SignIn(Constants.Provider.Github, new SignInOptions
|
||||
{
|
||||
FlowType = Constants.OAuthFlowType.PKCE,
|
||||
RedirectTo = "http://localhost:3000/oauth/callback"
|
||||
});
|
||||
|
||||
// In callback received from Supabase returning to RedirectTo (set above)
|
||||
// Url is set as: http://REDIRECT_TO_URL?code=CODE
|
||||
var session = await client.ExchangeCodeForSession(state.PKCEVerifier, RETRIEVE_CODE_FROM_GET_PARAMS);
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Q: I've created a User but while attempting to log in it throws an exception:**
|
||||
|
||||
A: Provided the credentials are correct, make sure that the User has also confirmed their email.
|
||||
|
||||
Adding a handler for email confirmation to a desktop or mobile application can be done, but it
|
||||
requires setting up URL handlers for each platform, which can be pretty difficult to do if you
|
||||
aren't really comfortable with configuring these handlers. (
|
||||
e.g. [Windows](https://learn.microsoft.com/en-us/windows/win32/search/-search-3x-wds-ph-install-registration),
|
||||
[Apple](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app),
|
||||
[Android](https://developer.android.com/training/app-links))
|
||||
You may find it easier to create a
|
||||
simple web application to handle email confirmation - that way a user can just click a link in
|
||||
their email and get confirmed that way. Your desktop or mobile app should inspect the user object
|
||||
that comes back and use that to see if the user is confirmed.
|
||||
|
||||
You might find it easiest to do something like create and deploy a
|
||||
simple [SvelteKit](https://kit.svelte.dev/) or even a very basic
|
||||
pure [JavaScript](https://github.com/supabase/examples-archive/tree/main/supabase-js-v1/auth/javascript-auth) project
|
||||
to handle email verification.
|
||||
|
||||
## Status
|
||||
|
||||
- [x] API
|
||||
- [x] Sign Up with Email
|
||||
- [x] Sign In with Email
|
||||
- [x] Send Magic Link Email
|
||||
- [x] Invite User by Email
|
||||
- [x] Reset Password for Email
|
||||
- [x] Signout
|
||||
- [x] Get Url for Provider
|
||||
- [x] Get User
|
||||
- [x] Update User
|
||||
- [x] Refresh Access Token
|
||||
- [x] List Users (includes filtering, sorting, pagination)
|
||||
- [x] Get User by Id
|
||||
- [x] Create User
|
||||
- [x] Update User by Id
|
||||
- [x] Client
|
||||
- [x] Get User
|
||||
- [x] Refresh Session
|
||||
- [x] Auth State Change Handler
|
||||
- [x] Provider Sign In (Provides URL)
|
||||
- [x] Provide Interfaces for Custom Token Persistence Functionality
|
||||
- [x] Documentation
|
||||
- [x] Unit Tests
|
||||
- [x] Nuget Release
|
||||
|
||||
## Package made possible through the efforts of:
|
||||
|
||||
Join the ranks! See a problem? Help fix it!
|
||||
|
||||
<a href="https://github.com/supabase-community/gotrue-csharp/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=supabase-community/gotrue-csharp" />
|
||||
</a>
|
||||
|
||||
<small>Made with [contrib.rocks](https://contrib.rocks).</small>
|
||||
|
||||
## Contributing
|
||||
|
||||
We are more than happy to have contributions! Please submit a PR.
|
||||
|
||||
### Testing
|
||||
|
||||
To run the tests locally you must have docker and docker-compose installed. Then in the root of the repository run:
|
||||
|
||||
- `docker-compose up -d`
|
||||
- `dotnet test`
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52efbfb7399c78c4ba42c9ee57f37b62
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>gotrue-csharp</id>
|
||||
<version>4.2.7</version>
|
||||
<title>Gotrue</title>
|
||||
<authors>Joseph Schultz <joseph@acupofjose.com></authors>
|
||||
<license type="expression">MIT</license>
|
||||
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
|
||||
<icon>icon.png</icon>
|
||||
<readme>README.md</readme>
|
||||
<projectUrl>https://github.com/supabase-community/gotrue-csharp</projectUrl>
|
||||
<iconUrl>https://avatars.githubusercontent.com/u/54469796?s=200&v=4</iconUrl>
|
||||
<description>A C# client for gotrue</description>
|
||||
<copyright>MIT</copyright>
|
||||
<tags>supabase, gotrue</tags>
|
||||
<repository type="git" url="https://github.com/supabase-community/gotrue-csharp" commit="ffd11aa826dd24b706d5c65615346670b823965c" />
|
||||
<dependencies>
|
||||
<group targetFramework=".NETStandard2.0">
|
||||
<dependency id="Newtonsoft.Json" version="13.0.3" exclude="Build,Analyzers" />
|
||||
<dependency id="System.IdentityModel.Tokens.Jwt" version="7.0.3" exclude="Build,Analyzers" />
|
||||
<dependency id="supabase-core" version="0.0.3" exclude="Build,Analyzers" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
</package>
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 178e110839dfff043bbe3b561a5b1714
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5521a79b4acd1013574150313315a0ec9093eca0072a4b1f6052ac1ef0c988c9
|
||||
size 5771
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d51fe9391d63f64c90a2243adb7ea2f
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38c4bc715af26774c9e5b972b6b0d7d0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 099cc5d1f4558e54bbd2cebffd748273
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1266c94191ef2104cae2c91486598fb2
|
||||
labels:
|
||||
- NuGetForUnity
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 3
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
Any:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
Editor:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
WindowsStoreApps:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1d8df427a74bdf488908b0c96b857af
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user