User Management
User management logic can be implemented in 3 ways:
The logic is implemented in the frontend / webapp and a cloud function acts as an authorization proxy to the automatically created GraphQL Backend
Example Code
import { gql } from "https://deno.land/x/graphql_tag@0.0.1/mod.ts" import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts" const gqlApi = "http://localhost:4185/dbs/<TODO>/graphql" interface User { id: number email: string name: string password_hash?: string } async function saveUserWithToken(user: User, token: string) { const response = await fetch(gqlApi, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: ` mutation UpdateSessionToken( $user: users_set_input!, $usersFilter: users_filter! ) { update_users(filter: $usersFilter, set: $user) { affected_rows } } `, variables: { usersFilter: { email: { eq: user.email, }, }, user: { email: user.email, session_token: token, }, }, }), }) if (response.ok) { return { data: { sessionToken: token } } } else { return { errors: [{ message: "Failed to save session" }] } } } async function loadUserFromToken( bearerToken: string, ): Promise<User | null> { const token = bearerToken.replace(/^Bearer /, "") const response = await fetch(gqlApi, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: ` query GetUserForToken($userFilter: users_filter) { users(filter: $userFilter) { rowid email first_name last_name session_token } } `, variables: { userFilter: { session_token: { eq: token } }, }, }), }) if (response.ok) { const jsonResponse = await response.json() const user = jsonResponse.data?.users?.[0] if (user) { user.id = user.rowid user.name = user.first_name + " " + user.last_name delete user.session_token return user } } else { console.error(response) } return null } async function loadUsers(): Promise<User[]> { // Password hash: BCrypt // https://gchq.github.io/CyberChef/#recipe=Bcrypt(10) const response = await fetch(gqlApi, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: ` query GetUsers { users { email password_hash } } `, }), }) if (response.ok) { const jsonResponse = await response.json() return jsonResponse?.data?.users } else { console.error(response) return [] } } interface GQLResponse { data?: Record<string, any> errors?: { message: string }[] } export default async function (context): Promise<GQLResponse> { const gqlQuery = context.data.query const queryName = gql` ${gqlQuery} `?.definitions[0]?.name?.value if (queryName === "LoginRequest") { const argsObj = gql` ${gqlQuery} `?.definitions[0]?.selectionSet?.selections[0]?.arguments const email = argsObj?.find((arg) => arg.name.value === "email")?.value ?.value const password = argsObj?.find((arg) => arg.name.value === "password") ?.value?.value // const rememberMe = argsObj // ?.find(arg => arg.name.value === "rememberMe") // ?.value // ?.value if (email && password) { const users = await loadUsers() const user = users.find((user) => user.email === email) if (user && bcrypt.compareSync(password, user?.password_hash)) { user.name = "" const randomToken = Math.random().toString(36).substring(2) const saveResult = await saveUserWithToken(user, randomToken) return saveResult } else { return { errors: [ { message: "User does not exist or an incorrect password was specified", }, ], } } } } if (queryName === "UserRequest") { return { data: await loadUserFromToken(context.headers.Authorization) } } const response = await fetch(gqlApi, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ query: gqlQuery }), }) if (response.ok) { const jsonResponse = await response.json() return jsonResponse } else { return { errors: [{ message: "Failed to fetch data" }], } } }
The whole logic is implemented in cloud functions which provide their own JSON API. The database access can then either be implemented by making GraphQL requests to the GraphQL resolver or by directly accessing the SQLite database. (Use the example code from 1. and extend it with business logic functions that write directly to the database as seen in the functions documentation page)
The logic is implemented in an additional external server (written in any language) which provides a JSON or GraphQL API to your APP and calls Airsequel’s GraphQL API to access the database.
Here is an architecture diagram of the involved components: