@riseact/riseact-node-sdk
v6.0.6
Published
Riseact SDK for Nodejs
Downloads
55
Keywords
Readme
riseact-node-sdk
Riseact SDK for Node.js to develop Riseact apps.
Installation
npm install @riseact/riseact-node-sdk
# or
yarn add @riseact/riseact-node-sdkPeer dependencies
Install the following peer dependencies if you don't have them already:
npm install express@^4.18
# or
yarn add express@^4.18Usage
Refer to riseact-app-template-node repo for a complete example of how to use this SDK in a Node.js app.
const RiseactConfig: RiseactConfig = {
// Provide your application ID and secret from Riseact
auth: {
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
storage: {
// Memory, file or custom storage. For custom, you need to define the adapters
type: 'file',
},
network: {
// The URL of the Riseact API. Use the public URL for production and ngrok URL/tunnel for development.
appPublicUrl: process.env.RISEACT_APP_URL,
},
// Optional: In development, you can define custom paths for the Vite configuration and root directory of your client application.
...(process.env.NODE_ENV === 'development' && {
dev: {
// Provide the path to your Vite configuration file and the root directory of your client application.
viteConfig: {
root: path.join(process.cwd(), '../client'),
configFile: path.join(process.cwd(), '../client/vite.config.ts'),
},
},
}),
};
async function createServer() {
const app: Express = express();
// Create the Riseact SDK instance with the client ID and client secret generated from Riseact
const riseact = await initRiseactSDK(RiseactConfig);
// Creare a public router for public endpoints
const publicEpsRouter = express.Router();
publicEpsRouter.use(
riseact.network.registerWebhook(WebhookEventTopic.SupporterCreated, (data) => {
console.log('Webhook received', data);
}),
);
// Create a protected router for private endpoints (request inside Riseact Admin iframe)
const privateEpsRouter = express.Router();
privateEpsRouter.get('/api/hello', (req, res) => {
res.send('Hello World!');
});
privateEpsRouter.get('/api/organization-info', OrganizationInfoHandler(riseact));
privateEpsRouter.get('/api/organization-credentials', OrganizationCredentialsHandler());
// Start the Riseact app
riseact.utils.startRiseactApp(app, riseact, {
publicRouter: publicEpsRouter,
protectedRouter: privateEpsRouter,
serverPort: process.env.SERVER_PORT ? parseInt(process.env.SERVER_PORT) : 3000,
});
}
createServer();
GQL Client
For information about the Riseact GQL Schema, explore it yourself with ApolloStudio typing https://core.riseact.org/admin/graphql/
const ORGANIZATION_INFO_QUERY = graphql(`
query GetOrganizationInfo {
organization {
name
logo {
square
}
}
}
`);
export const OrganizationInfoHandler = async (req, res) => {
// Create a GraphQL client for the user's organization
const graphqlClient = await req.riseact.network.createGqlClient(req.organizationDomain);
// Get the organization type from the common package
const { data, error } = await graphqlClient.query<OrganizationInfoResponseQuery>({
query: ORGANIZATION_INFO_QUERY,
});
if (error) {
return res.status(500);
}
// Return the organization data
res.json({
name: data.organization.name,
logoUrl: data.organization.logo?.square,
});
}Development
Authentication Architecture
Actors
- Client: browser SPA, server inside the Riseact Admin application iframe
- App backend: Node server under your control
- Riseact API: external GraphQL service with OAuth 2 (Authorization-Code + PKCE).
Auth flow
- App client hits an app backend route; the backend redirects it to Riseact’s authorize endpoint with code-challenge.
- Riseact calls the backend callback with the OAuth credentials.
- Backend generates a long-lasting client token, stores it together with Riseact credentials, and returns the client token to the browser as a cookie.
Requests flow from the client
- App client sends a GraphQL HTTP request to the App backend, attaching its client token cookie.
- Backend looks up the stored Riseact credentials by that token and forwards the original request to Riseact adding
Authorization: Bearer <access_token>. - Riseact answers; backend streams the response back to the browser unchanged.
Transparent token-renewal loop
- If Riseact returns an auth error (token expired or revoked), the backend:
- Performs a single-flight refresh with the stored
refresh_token. - Retries the identical request with the new
access_token. - On success, relays the data to the client; on failure (refresh denied) it redirects the browser to restart the OAuth flow.
- Performs a single-flight refresh with the stored
- This approach covers both normal expiry and out-of-band revocation without relying on local expiry timestamps.
- If Riseact returns an auth error (token expired or revoked), the backend:
Two query surfaces available
Frontend → App Backend → Riseact
- Browser always calls the backend; backend proxies to Riseact.
- No Riseact tokens ever reach the browser.
- Suits browser-only environments, keeps CORS simple.
Backend-only queries
- Internal services within the app backend can call Riseact directly using the same stored credentials—no proxy hop and no client token involved.
- Ideal for scheduled jobs or server-to-server logic.
Auth flow
Authentication Flow
authMiddleware secures every request originating from the Riseact Admin iframe, which always appends __organization=<org-domain> to the URL.
The auth middleware does the following:
Intercept the
/oauth/authorizeroute- Rejects with 400 if
__organizationis absent. - Determines
redirect_urifrom theappPublicUrlprovided in configuration - Instantiates an OAuth client with
client_id,client_secret, and the computedredirect_uri. - Generates a PKCE
code_verifiervia openid-client. - Sets cookie
X-Riseact-Sdk-Code-Verifierthat stores thecode_verifierand the organization domain. - Redirects the browser to Riseact’s authorization endpoint.
- Rejects with 400 if
Intercept the
/oauth/callbackroute- Requires the
X-Riseact-Sdk-Code-Verifiercookie (returns 401 if missing). - Validates the saved
code_verifieragainst thecode_challenge. If invalid returns 403. - Extracts
access_token,refresh_token, andexpires_infrom the query string. - Computes
expires_atfromexpires_in+now(). - Generates a UUID v4
client_token. - Save
{ access_token, refresh_token, client_token, expires_in, expires_at, organization_domain }in the storage.
- Requires the
All other routes
- Looks for a
X-Riseact-Sdk-Tokencookie; if absent, redirects to/oauth/authorize?__organization=<org-domain>. - JSON-parses the cookie and validates
client_tokenandorganization_domain. - Retrieves credentials by
client_token; if missing, redirects to authorize. - Ensures the cookie’s
organization_domainmatches the stored record; if mismatch, redirects to/authorize - Adds
req.organizationDomainand callsnext().
- Looks for a
Stored credential model
| Field | Purpose |
| --------------------- | ----------------------------------------- |
| access_token | Bearer token for Riseact GraphQL calls |
| refresh_token | Used to renew the access token |
| client_token | Long-lived identifier for the browser |
| expires_in | TTL of the current access token (seconds) |
| expires_at | UTC timestamp when the token expires |
| organization_domain | Tenant identifier |
createGqlClient consumes these credentials to build an authenticated Apollo client for Riseact. It automatically handles token renewal and retries failed requests.
Riseact errors
Riseact backend do not throw errors. If user is not authenticated, it respond with a 200 with this payload:
{
"data": null,
"errors": [
{
"message": "User is not authenticated",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"path"
]
}
]
}SDK Development
Create a .env file in the root of your project and add the following lines:
CLIENT_ID=<your-app-client-id>
CLIENT_SECRET=<your-app-client-secret>Run the development server with:
riseact app devNow the changes you make to the SDK will be reflected in the dev app.
License
MIT
