带有 Apollo SubscriptionClient 的动态 Hasura X-Hasura-Role
Dynamic Hasura X-Hasura-Role with Apollo SubscriptionClient
根据 Hasura 中的用户角色动态更改 Apollo SubscriptionClient 中的 headers 的正确方法是什么?
堆栈:
- NextJS
- 阿波罗
- Auth0
- Hasura(商店 Auth0_Id 具有角色列)
此处的目标是利用 Hasura 角色获得权限。我知道 JWT 令牌具有允许的角色,但我希望能够根据用户分配的角色设置角色。我要走的路是通过 NextJS 内部 API.
向 Hasura 用户 table 查询具有来自 auth0 的用户 ID 的角色
ApolloClient.js
import fetch from 'isomorphic-unfetch'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http'
import { onError } from 'apollo-link-error'
import { WebSocketLink } from 'apollo-link-ws'
import { SubscriptionClient } from 'subscriptions-transport-ws'
let accessToken, role, user = null
const requestAccessToken = async () => {
if (accessToken) return
const res = await fetch(`${process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI}/api/session`)
if (res.ok) {
const json = await res.json()
accessToken = json.accessToken
} else {
accessToken = 'public'
}
}
const requestRole = async (userId) => {
if (role) return
const res = await fetch(`${process.env.NEXT_PUBLIC_APP_HOST}/api/role/"${userId}"`)
if (res.ok) {
const json = await res.json()
console.log(json)
role = json.data.vknursery_users_by_pk.role
}
}
const requestUser = async () => {
if (role) return
const res = await fetch(`${process.env.NEXT_PUBLIC_APP_HOST}/api/me`)
if (res.ok) {
const json = await res.json()
user = json
}
}
// remove cached token on 401 from the server
const resetTokenLink = onError(({ networkError }) => {
if (networkError && networkError.name === 'ServerError' && networkError.statusCode === 401) {
accessToken = null
}
})
const createHttpLink = (headers) => {
const httpLink = new HttpLink({
uri: `https://${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/graphql`,
credentials: 'include',
headers, // auth token is fetched on the server side
fetch,
})
return httpLink;
}
const createWSLink = () => {
return new WebSocketLink(
new SubscriptionClient(`wss://${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/graphql`, {
lazy: true,
reconnect: true,
connectionParams: async () => {
await requestAccessToken() // happens on the client
await requestUser()
await requestRole(user.sub) //get role from hasura to assign in apollo request headers
return {
headers: {
'X-Hasura-Role': role,
authorization: accessToken ? `Bearer ${accessToken}` : '',
},
}
},
})
)
}
export default function createApolloClient(initialState, headers) {
const ssrMode = typeof window === 'undefined'
let link
if (ssrMode) {
link = createHttpLink(headers) // executed on server
} else {
link = createWSLink() // executed on client
}
return new ApolloClient({
ssrMode,
link,
cache: new InMemoryCache().restore(initialState),
})
}
/api/role/[userId].js
import fetch from 'isomorphic-unfetch';
export default async function me(req, res) {
const {
query: { userId },
} = req
try {
await fetch(`https://${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/graphql`, {
method: 'POST',
headers: {
'x-hasura-admin-secret': process.env.HASURA_GRAPHQL_ADMIN_SECRET,
'Content-Type': 'application/json'
},
body: JSON.stringify({ query: `{ vknursery_users_by_pk( auth0_id:${userId}){ role } }` })
})
.then(r => r.json())
.then(data => {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
console.log(JSON.stringify(data));
res.end(JSON.stringify(data))
});
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
}
/api/me.js
import auth0 from '../../lib/auth0'
export default async function me(req, res) {
try {
await auth0.handleProfile(req, res)
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
}
该角色要么作为与用户 table 的连接(您可以在此处保留 auth0 id),要么作为由 auth0 签名的 JWT 的 一部分(有其 UI) 或 作为身份验证挂钩的一部分的角色功能 (请参阅 Hasura 的文档)。调用客户端不提供角色,因为通常您不控制其执行。
根据 Hasura 中的用户角色动态更改 Apollo SubscriptionClient 中的 headers 的正确方法是什么?
堆栈:
- NextJS
- 阿波罗
- Auth0
- Hasura(商店 Auth0_Id 具有角色列)
此处的目标是利用 Hasura 角色获得权限。我知道 JWT 令牌具有允许的角色,但我希望能够根据用户分配的角色设置角色。我要走的路是通过 NextJS 内部 API.
向 Hasura 用户 table 查询具有来自 auth0 的用户 ID 的角色ApolloClient.js
import fetch from 'isomorphic-unfetch'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http'
import { onError } from 'apollo-link-error'
import { WebSocketLink } from 'apollo-link-ws'
import { SubscriptionClient } from 'subscriptions-transport-ws'
let accessToken, role, user = null
const requestAccessToken = async () => {
if (accessToken) return
const res = await fetch(`${process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI}/api/session`)
if (res.ok) {
const json = await res.json()
accessToken = json.accessToken
} else {
accessToken = 'public'
}
}
const requestRole = async (userId) => {
if (role) return
const res = await fetch(`${process.env.NEXT_PUBLIC_APP_HOST}/api/role/"${userId}"`)
if (res.ok) {
const json = await res.json()
console.log(json)
role = json.data.vknursery_users_by_pk.role
}
}
const requestUser = async () => {
if (role) return
const res = await fetch(`${process.env.NEXT_PUBLIC_APP_HOST}/api/me`)
if (res.ok) {
const json = await res.json()
user = json
}
}
// remove cached token on 401 from the server
const resetTokenLink = onError(({ networkError }) => {
if (networkError && networkError.name === 'ServerError' && networkError.statusCode === 401) {
accessToken = null
}
})
const createHttpLink = (headers) => {
const httpLink = new HttpLink({
uri: `https://${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/graphql`,
credentials: 'include',
headers, // auth token is fetched on the server side
fetch,
})
return httpLink;
}
const createWSLink = () => {
return new WebSocketLink(
new SubscriptionClient(`wss://${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/graphql`, {
lazy: true,
reconnect: true,
connectionParams: async () => {
await requestAccessToken() // happens on the client
await requestUser()
await requestRole(user.sub) //get role from hasura to assign in apollo request headers
return {
headers: {
'X-Hasura-Role': role,
authorization: accessToken ? `Bearer ${accessToken}` : '',
},
}
},
})
)
}
export default function createApolloClient(initialState, headers) {
const ssrMode = typeof window === 'undefined'
let link
if (ssrMode) {
link = createHttpLink(headers) // executed on server
} else {
link = createWSLink() // executed on client
}
return new ApolloClient({
ssrMode,
link,
cache: new InMemoryCache().restore(initialState),
})
}
/api/role/[userId].js
import fetch from 'isomorphic-unfetch';
export default async function me(req, res) {
const {
query: { userId },
} = req
try {
await fetch(`https://${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/graphql`, {
method: 'POST',
headers: {
'x-hasura-admin-secret': process.env.HASURA_GRAPHQL_ADMIN_SECRET,
'Content-Type': 'application/json'
},
body: JSON.stringify({ query: `{ vknursery_users_by_pk( auth0_id:${userId}){ role } }` })
})
.then(r => r.json())
.then(data => {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
console.log(JSON.stringify(data));
res.end(JSON.stringify(data))
});
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
}
/api/me.js
import auth0 from '../../lib/auth0'
export default async function me(req, res) {
try {
await auth0.handleProfile(req, res)
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
}
该角色要么作为与用户 table 的连接(您可以在此处保留 auth0 id),要么作为由 auth0 签名的 JWT 的 一部分(有其 UI) 或 作为身份验证挂钩的一部分的角色功能 (请参阅 Hasura 的文档)。调用客户端不提供角色,因为通常您不控制其执行。