add: app sdk client auth flow example

This commit is contained in:
trisua 2025-07-19 02:00:04 -04:00
parent 0138bf4cd4
commit 9ccbc69405
9 changed files with 95 additions and 27 deletions

View file

@ -112,7 +112,7 @@ macro_rules! get_user_from_token {
Ok((grant, ua)) => { Ok((grant, ua)) => {
if grant.scopes.contains(&$grant_scope) { if grant.scopes.contains(&$grant_scope) {
if ua.permissions.check_banned() { if ua.permissions.check_banned() {
Some(tetratto_core::model::auth::User::banned()) None
} else { } else {
Some(ua) Some(ua)
} }

View file

@ -180,7 +180,8 @@
(li (b (text "Redirect URL: ")) (text "{{ app.redirect }}")) (li (b (text "Redirect URL: ")) (text "{{ app.redirect }}"))
(li (b (text "Quota status: ")) (text "{{ app.quota_status }}")) (li (b (text "Quota status: ")) (text "{{ app.quota_status }}"))
(li (b (text "User grants: ")) (text "{{ app.grants }}")) (li (b (text "User grants: ")) (text "{{ app.grants }}"))
(li (b (text "Grant URL: ")) (text "{{ config.host }}/auth/connections_link/app/{{ app.id }}"))) (li (b (text "Grant URL: ")) (text "{{ config.host }}/auth/connections_link/app/{{ app.id }}"))
(li (b (text "App ID (for SDK): ")) (text "{{ app.id }}")))
(a (a
("class" "button") ("class" "button")

View file

@ -41,8 +41,7 @@
("id" "homepage") ("id" "homepage")
("placeholder" "homepage") ("placeholder" "homepage")
("required" "") ("required" "")
("minlength" "2") ("minlength" "2")))
("maxlength" "32")))
(div (div
("class" "flex flex-col gap-1") ("class" "flex flex-col gap-1")
(label (label
@ -53,8 +52,7 @@
("name" "redirect") ("name" "redirect")
("id" "redirect") ("id" "redirect")
("placeholder" "redirect URL") ("placeholder" "redirect URL")
("minlength" "2") ("minlength" "2")))
("maxlength" "32")))
(button (button
(text "{{ text \"communities:action.create\" }}")))) (text "{{ text \"communities:action.create\" }}"))))

View file

@ -176,11 +176,7 @@ export default function tetratto({
window.localStorage.setItem("atto:grant.user_id", uid); window.localStorage.setItem("atto:grant.user_id", uid);
} }
async function refresh_token(verifier) { async function refresh_token() {
if (!user_token) {
throw Error("No user token provided.");
}
return api_promise( return api_promise(
json_parse( json_parse(
await ( await (
@ -190,10 +186,10 @@ export default function tetratto({
method, method,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Cookie": `__Secure-atto-token=${user_token}`, "X-Cookie": `Atto-Grant=${user_token}`,
}, },
body: json_stringify({ body: json_stringify({
verifier, verifier: user_verifier,
}), }),
}, },
) )
@ -203,10 +199,10 @@ export default function tetratto({
} }
async function request({ async function request({
api_path, route,
method = "POST", method = "POST",
content_type = "application/json", content_type = "application/json",
body = "{}", body = {},
}) { }) {
if (!user_token) { if (!user_token) {
throw Error("No user token provided."); throw Error("No user token provided.");
@ -215,16 +211,19 @@ export default function tetratto({
return api_promise( return api_promise(
json_parse( json_parse(
await ( await (
await fetch(`${host}/api/v1/${api_path}`, { await fetch(`${host}/api/v1/${route}`, {
method, method,
headers: { headers: {
"Content-Type": content_type, "Content-Type":
"X-Cookie": `__Secure-atto-token=${user_token}`, method === "GET" ? null : content_type,
"X-Cookie": `Atto-Grant=${user_token}`,
}, },
body: body:
content_type === "application/json" method === "GET"
? json_stringify(body) ? null
: body, : content_type === "application/json"
? json_stringify(body)
: body,
}) })
).text(), ).text(),
), ),
@ -233,6 +232,11 @@ export default function tetratto({
// ... // ...
return { return {
user_id,
user_token,
user_verifier,
app_id,
api_key,
// app data // app data
app, app,
query, query,

View file

@ -196,8 +196,8 @@ impl DataManager {
let res = query_row!( let res = query_row!(
&conn, &conn,
"SELECT * FROM users WHERE (SELECT jsonb_array_elements(grants::jsonb) @> ('{\"token\":\"' || $1 || '\"}')::jsonb)", "SELECT * FROM users WHERE grants LIKE $1",
&[&token], &[&format!("%\"token\":\"{token}\"%")],
|x| Ok(Self::get_user_from_row(x)) |x| Ok(Self::get_user_from_row(x))
); );

1
example/.gitignore vendored
View file

@ -4,6 +4,7 @@ html/*
public/* public/*
!public/footer.html !public/footer.html
!public/robots.txt !public/robots.txt
!public/examples
media/* media/*
icons/* icons/*
langs/* langs/*

View file

@ -1,10 +1,10 @@
// @ts-nocheck // @ts-nocheck
// APP_API_KEY=... deno run --allow-net --allow-import --allow-env -r app_sdk_test.js // APP_API_KEY=... deno run --allow-net --allow-import --allow-env -r app_sdk_test.js
const deno = Deno; const deno = Deno;
const sdk = (await import("http://localhost:4118/js/app_sdk.js")).default( const sdk = (await import("http://localhost:4118/js/app_sdk.js")).default({
"http://localhost:4118", host: "http://localhost:4118",
deno.env.get("APP_API_KEY"), api_key: deno.env.get("APP_API_KEY"),
); });
// check data used // check data used
console.log("data used:", (await sdk.app()).data_used); console.log("data used:", (await sdk.app()).data_used);

View file

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Auth flow example</title>
</head>
<body>
<button
onclick="window.location.href = globalThis.sdk.GRANT_URL"
id="login_button"
>
Login
</button>
<p id="greeting"></p>
<script type="module">
const app_id = BigInt(prompt("App ID:"));
globalThis.sdk = (await import("/js/app_sdk.js")).from_localstorage(
{
host: window.location.origin,
app_id,
},
);
if (sdk.user_id) {
const user = await sdk.request({
route: "auth/user/me",
method: "GET",
});
document.getElementById("login_button").remove();
document.getElementById("greeting").innerText =
`Hello, ${user.username}!`;
}
</script>
</body>
</html>

View file

@ -0,0 +1,25 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Auth flow example redirect</title>
</head>
<body>
<p>Waiting...</p>
<script type="module">
const app_id = BigInt(prompt("App ID:"));
globalThis.sdk = (await import("/js/app_sdk.js")).from_localstorage(
{
host: window.location.origin,
app_id,
},
);
sdk.localstorage_accept_connection();
window.location.href =
"/public/examples/auth_flow_example/index.html";
</script>
</body>
</html>