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/brisk-otter-glides.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/server": patch
---

🛂 add org auth middleware
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
Comment thread
nfmelendez marked this conversation as resolved.
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");
Comment thread
nfmelendez marked this conversation as resolved.

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",
Comment thread
nfmelendez marked this conversation as resolved.
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
},
Comment thread
nfmelendez marked this conversation as resolved.
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");
Comment thread
nfmelendez marked this conversation as resolved.

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 },
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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);
});
Comment thread
nfmelendez marked this conversation as resolved.
```
Loading
Loading