Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pretty-chicken-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/server": patch
---

➕ install better auth
5 changes: 5 additions & 0 deletions .changeset/rare-pears-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/server": patch
---

🛂 setup better auth
2 changes: 1 addition & 1 deletion docs/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default defineConfig({
{ base: "api", schema: "node_modules/@exactly/server/generated/openapi.json", sidebar: { collapsed: false } },
]),
],
sidebar: openAPISidebarGroups,
sidebar: [{ label: "Docs", items: ["index", "organization-authentication"] }, ...openAPISidebarGroups],
}),
mermaid(),
],
Expand Down
143 changes: 143 additions & 0 deletions docs/src/content/docs/organization-authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
title: Organizations, authentication and authorization
sidebar:
label: Organizations and authentication
order: 10
---

Creating organizations is permission-less. Any user can create an organization and will be the owner.
Then the owner can add members with admin role and those admins will be able to add more members with different roles.

Better auth client and viem are the recommended libraries to use for authentication and signing using SIWE.

## SIWE Authentication

Example code to authenticate using SIWE, it will create the user if doesn't exist.
Note: Check viem account to use a private key instead of a mnemonic.

```typescript
import { createAuthClient } from "better-auth/client";
import { siweClient, organizationClient } from "better-auth/client/plugins";
import { mnemonicToAccount } from "viem/accounts";
import { optimismSepolia } from "viem/chains";
import { createSiweMessage } from "viem/siwe";

const chainId = optimismSepolia.id;

const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [siweClient(), organizationClient()],
});

const owner = mnemonicToAccount("test test test test test test test test test test test test");

authClient.siwe
.nonce({
walletAddress: owner.address,
chainId,
})
.then(async ({ data: nonceResult }) => {
//can be any statement
const statement = "i accept exa terms and conditions";
const nonce = nonceResult?.nonce ?? "";
const message = createSiweMessage({
statement,
resources: ["https://exactly.github.io/exa"],
nonce,
uri: "https://localhost",
address: owner.address,
chainId,
scheme: "https",
version: "1",
domain: "localhost",
});
const signature = await owner.signMessage({ message });

await authClient.siwe.verify(
{
message,
signature,
walletAddress: owner.address,
chainId,
},
{
onSuccess: async (context) => {
// authentication successful, session cookie is now set
},
onError: (context) => {
console.log("authorization error", context);
},
},
);
}).catch((error: unknown) => {
console.error("nonce error", error);
});
```

## Creating an organization

owner account will be the owner of the created organization

```typescript
const chainId = optimismSepolia.id;

const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [siweClient(), organizationClient()],
});

const owner = mnemonicToAccount("test test test test test test test test test test test siwe");

authClient.siwe
.nonce({
walletAddress: owner.address,
chainId,
})
.then(async ({ data: nonceResult }) => {
const statement = `i accept exa terms and conditions`;
const nonce = nonceResult?.nonce ?? "";
const message = createSiweMessage({
statement,
resources: ["https://exactly.github.io/exa"],
nonce,
uri: `https://localhost`,
address: owner.address,
chainId,
scheme: "https",
version: "1",
domain: "localhost",
});
const signature = await owner.signMessage({ message });

await authClient.siwe.verify(
{
message,
signature,
walletAddress: owner.address,
chainId,
},
{
onSuccess: async (context) => {
const headers = new Headers();
headers.set("cookie", context.response.headers.get("set-cookie") ?? "");
const createOrganizationResult = await authClient.organization.create({
fetchOptions: { headers },
name: "Uphold",
slug: "uphold",
keepCurrentActiveOrganization: false,
});
if (createOrganizationResult.data) {
console.log(`organization created id: ${createOrganizationResult.data.id}`);
} else {
console.error("Failed to create organization error:", createOrganizationResult.error);
}
},
onError: (context) => {
console.log("authorization error", context);
},
},
);
}).catch((error: unknown) => {
console.error("nonce error", error);
});
```
Loading
Loading