This commit is contained in:
2025-12-03 01:24:52 -03:00
parent 3a49b13cb9
commit 38c49ff370
21 changed files with 133 additions and 73 deletions

View File

@@ -117,9 +117,10 @@ def _create_user(user: User, org: Org) -> bool:
'sk': '0', 'sk': '0',
'email_verified': email_verified, 'email_verified': email_verified,
'tenant_id': {org.id}, 'tenant_id': {org.id},
# Post-migration: uncomment the folloing line # Post-migration (users): uncomment the folloing line
# 'org_id': {org.id}, # 'org_id': {org.id},
'created_at': now_, # 'created_at': now_,
'createDate': now_,
}, },
) )
transact.put( transact.put(
@@ -157,9 +158,10 @@ def _create_user(user: User, org: Org) -> bool:
transact.put( transact.put(
item={ item={
# Post-migration: rename `cpf` to `CPF` # Post-migration (users): rename `cpf` to `CPF`
'id': 'cpf', 'id': 'cpf',
'sk': user.cpf, 'sk': user.cpf,
'user_id': user_id,
'created_at': now_, 'created_at': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
@@ -167,9 +169,10 @@ def _create_user(user: User, org: Org) -> bool:
) )
transact.put( transact.put(
item={ item={
# Post-migration: rename `email` to `EMAIL` # Post-migration (users): rename `email` to `EMAIL`
'id': 'email', 'id': 'email',
'sk': user.email, 'sk': user.email,
'user_id': user_id,
'created_at': now_, 'created_at': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
@@ -179,7 +182,7 @@ def _create_user(user: User, org: Org) -> bool:
item={ item={
'id': user_id, 'id': user_id,
'sk': f'orgs#{org.id}', 'sk': f'orgs#{org.id}',
# Post-migration: uncomment the following line # Post-migration (users): uncomment the following line
# pk=f'ORG#{org.id}', # pk=f'ORG#{org.id}',
'name': org.name, 'name': org.name,
'cnpj': org.cnpj, 'cnpj': org.cnpj,
@@ -189,7 +192,7 @@ def _create_user(user: User, org: Org) -> bool:
transact.put( transact.put(
item={ item={
'id': f'orgmembers#{org.id}', 'id': f'orgmembers#{org.id}',
# Post-migration: uncomment the following line # Post-migration (users): uncomment the following line
# pk=f'MEMBER#ORG#{org_id}', # pk=f'MEMBER#ORG#{org_id}',
'sk': user_id, 'sk': user_id,
'created_at': now_, 'created_at': now_,
@@ -212,7 +215,7 @@ def _add_member(user_id: str, org: Org) -> None:
with dyn.transact_writer() as transact: with dyn.transact_writer() as transact:
transact.update( transact.update(
key=KeyPair(user_id, '0'), key=KeyPair(user_id, '0'),
# Post-migration: uncomment the following line # Post-migration (users): uncomment the following line
# update_expr='ADD tenant_id :org_id', # update_expr='ADD tenant_id :org_id',
update_expr='ADD tenant_id :org_id', update_expr='ADD tenant_id :org_id',
expr_attr_values={ expr_attr_values={
@@ -224,7 +227,7 @@ def _add_member(user_id: str, org: Org) -> None:
transact.put( transact.put(
item={ item={
'id': user_id, 'id': user_id,
# Post-migration: rename `orgs` to `ORG` # Post-migration (users): rename `orgs` to `ORG`
'sk': f'orgs#{org.id}', 'sk': f'orgs#{org.id}',
'name': org.name, 'name': org.name,
'cnpj': org.cnpj, 'cnpj': org.cnpj,
@@ -233,7 +236,8 @@ def _add_member(user_id: str, org: Org) -> None:
) )
transact.put( transact.put(
item={ item={
# Post-migration: rename `orgmembers` to `ORGMEMBER` # Post-migration (users): uncomment the following line
# pk=f'MEMBER#ORG#{org_id}',
'id': f'orgmembers#{org.id}', 'id': f'orgmembers#{org.id}',
'sk': user_id, 'sk': user_id,
'created_at': now_, 'created_at': now_,

View File

@@ -59,7 +59,7 @@ def get_user(user_id: str):
) )
if not user: if not user:
return UserNotFoundError('User not found') raise UserNotFoundError('User not found')
return user return user
@@ -74,10 +74,7 @@ def update(
old_cpf = dyn.collection.get_item( old_cpf = dyn.collection.get_item(
KeyPair( KeyPair(
pk=user_id, pk=user_id,
sk=SortKey( sk=SortKey('0', path_spec='cpf'),
'0',
path_spec='cpf',
),
), ),
) )

View File

@@ -37,6 +37,7 @@ export async function loader({ params, request, context }: Route.LoaderArgs) {
}) })
if (!r.ok) { if (!r.ok) {
console.log(r.status)
throw new Response(null, { status: r.status }) throw new Response(null, { status: r.status })
} }

View File

@@ -81,7 +81,7 @@ export default function Route({ loaderData }: Route.ComponentProps) {
<div className="ml-auto flex gap-2.5 items-center"> <div className="ml-auto flex gap-2.5 items-center">
<Notification /> <Notification />
<ModeToggle /> <ModeToggle />
<NavUser user={user} /> <NavUser user={user} excludeApps={['admin']} />
</div> </div>
</div> </div>
</header> </header>

View File

@@ -56,9 +56,7 @@ export async function action({ request, context }: Route.ActionArgs) {
try { try {
const r = await fetch(issuerUrl.toString(), { const r = await fetch(issuerUrl.toString(), {
method: 'POST', method: 'POST',
headers: { headers: new Headers({ 'Content-Type': 'application/json' }),
'Content-Type': 'application/json'
},
body: JSON.stringify(formData) body: JSON.stringify(formData)
}) })
@@ -88,10 +86,7 @@ export async function action({ request, context }: Route.ActionArgs) {
export default function Index({}: Route.ComponentProps) { export default function Index({}: Route.ComponentProps) {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const fetcher = useFetcher() const fetcher = useFetcher()
const form = useForm({ resolver: zodResolver(schema) })
const form = useForm({
resolver: zodResolver(schema)
})
const { control, handleSubmit, formState, setError } = form const { control, handleSubmit, formState, setError } = form
const onSubmit = async (data: Schema) => { const onSubmit = async (data: Schema) => {

View File

@@ -1,5 +1,6 @@
import type { Route } from '../+types' import type { Route } from '../+types'
import { useRequest } from 'ahooks'
import { PatternFormat } from 'react-number-format' import { PatternFormat } from 'react-number-format'
import { zodResolver } from '@hookform/resolvers/zod' import { zodResolver } from '@hookform/resolvers/zod'
import { useState } from 'react' import { useState } from 'react'
@@ -31,6 +32,19 @@ export function meta({}: Route.MetaArgs) {
return [{ title: 'Criar conta · EDUSEG®' }] return [{ title: 'Criar conta · EDUSEG®' }]
} }
export async function action({ request, context }: Route.ActionArgs) {
const issuerUrl = new URL('/register', context.cloudflare.env.ISSUER_URL)
const body = await request.json()
const r = await fetch(issuerUrl.toString(), {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(body)
})
console.log(await r.json())
}
export default function Signup({}: Route.ComponentProps) { export default function Signup({}: Route.ComponentProps) {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const [user, setUser] = useState<User | null>(null) const [user, setUser] = useState<User | null>(null)
@@ -38,13 +52,21 @@ export default function Signup({}: Route.ComponentProps) {
resolver: zodResolver(formSchema) resolver: zodResolver(formSchema)
}) })
const { control, handleSubmit, formState } = form const { control, handleSubmit, formState } = form
const { runAsync } = useRequest(
async (user) => {
return await fetch(`/register`, {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(user)
})
},
{ manual: true }
)
const onSubmit = async (data: Schema) => { const onSubmit = async (data: Schema) => {
console.log(data) await runAsync({ ...user, ...data })
} }
console.log(user)
return ( return (
<RegisterContext value={{ user, setUser }}> <RegisterContext value={{ user, setUser }}>
{user ? ( {user ? (

View File

@@ -6,6 +6,7 @@
], ],
"compilerOptions": { "compilerOptions": {
"checkJs": true, "checkJs": true,
"moduleResolution": "bundler",
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,

View File

@@ -48,7 +48,7 @@ export default function Route({ loaderData }: Route.ComponentProps) {
<div className="ml-auto flex gap-2.5 items-center"> <div className="ml-auto flex gap-2.5 items-center">
<ModeToggle /> <ModeToggle />
<NavUser user={user} /> <NavUser user={user} excludeApps={['insights']} />
</div> </div>
</div> </div>
</header> </header>

View File

@@ -176,10 +176,22 @@ function List({ s, hits = [] }: { s: string; hits: Enrollment[] }) {
<EmptyMedia variant="icon"> <EmptyMedia variant="icon">
<BanIcon /> <BanIcon />
</EmptyMedia> </EmptyMedia>
{s ? (
<>
<EmptyTitle>Nada encontrado</EmptyTitle> <EmptyTitle>Nada encontrado</EmptyTitle>
<EmptyDescription> <EmptyDescription>
Nenhum resultado para <mark>{s}</mark>. Nenhum resultado para <mark>{s}</mark>.
</EmptyDescription> </EmptyDescription>
</>
) : (
<>
<EmptyTitle>Nenhum curso ainda</EmptyTitle>
<EmptyDescription>
Comece escolhendo um curso. Assim que você se matricular, ele
aparecerá aqui.
</EmptyDescription>
</>
)}
</EmptyHeader> </EmptyHeader>
</Empty> </Empty>
) )

View File

@@ -141,7 +141,7 @@ export default function Component({
<div className="ml-auto flex gap-2.5 items-center"> <div className="ml-auto flex gap-2.5 items-center">
<ModeToggle /> <ModeToggle />
<NavUser user={user} /> <NavUser user={user} excludeApps={['saladeaula']} />
</div> </div>
</div> </div>
</header> </header>

View File

@@ -32,7 +32,7 @@ export default function Component({ loaderData }: Route.ComponentProps) {
<div className="ml-auto flex gap-2.5 items-center"> <div className="ml-auto flex gap-2.5 items-center">
<ModeToggle /> <ModeToggle />
<NavUser user={user} /> <NavUser user={user} excludeApps={['studio']} />
</div> </div>
</div> </div>
</header> </header>

View File

@@ -13,5 +13,11 @@ OAUTH2_SCOPES_SUPPORTED: list[str] = [
'apps:studio', 'apps:studio',
'apps:insights', 'apps:insights',
] ]
OAUTH2_DEFAULT_SCOPES = {
'email',
'offline_access',
'openid',
'profile',
}
SESSION_EXPIRES_IN = 86_400 * 30 # 30 days SESSION_EXPIRES_IN = 86_400 * 30 # 30 days

View File

@@ -21,7 +21,7 @@ from layercake.dynamodb import (
from layercake.funcs import omit, pick from layercake.funcs import omit, pick
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import ISSUER, OAUTH2_SCOPES_SUPPORTED, OAUTH2_TABLE from config import ISSUER, OAUTH2_DEFAULT_SCOPES, OAUTH2_SCOPES_SUPPORTED, OAUTH2_TABLE
from integrations.apigateway_oauth2.authorization_server import ( from integrations.apigateway_oauth2.authorization_server import (
AuthorizationServer, AuthorizationServer,
) )
@@ -191,10 +191,11 @@ class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
rename_key='scope', rename_key='scope',
), ),
) )
scope = set(user.get('scope', [])) | OAUTH2_DEFAULT_SCOPES
return User( return User(
**pick(('id', 'name', 'email', 'email_verified'), user), **pick(('id', 'name', 'email', 'email_verified'), user),
scope=' '.join(user['scope']) if 'scope' in user else None, scope=' '.join(scope),
) )

View File

@@ -5,13 +5,14 @@ from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import ( from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError, BadRequestError,
ForbiddenError, ForbiddenError,
NotFoundError,
ServiceError, ServiceError,
) )
from joserfc.errors import JoseError from joserfc.errors import JoseError
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import OAUTH2_TABLE from config import OAUTH2_DEFAULT_SCOPES, OAUTH2_TABLE
from oauth2 import server from oauth2 import server
from util import parse_cookies from util import parse_cookies
@@ -20,6 +21,9 @@ logger = Logger(__name__)
dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client) dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
class SessionNotFoundError(NotFoundError): ...
@router.get('/authorize') @router.get('/authorize')
def authorize(): def authorize():
current_event = router.current_event current_event = router.current_event
@@ -27,20 +31,20 @@ def authorize():
session = cookies.get('SID') session = cookies.get('SID')
if not session: if not session:
raise BadRequestError('Missing session') raise BadRequestError('Session cookie (SID) is required')
try: try:
sid, sub = session.split(':') session_id, user_id = session.split(':')
# Raise if session is not found # Raise if session is not found
dyn.collection.get_item( dyn.collection.get_item(
KeyPair('SESSION', sid), KeyPair('SESSION', session_id),
exc_cls=InvalidSession, exc_cls=SessionNotFoundError,
) )
grant = server.get_consent_grant( grant = server.get_consent_grant(
request=router.current_event, request=router.current_event,
end_user=sub, end_user=user_id,
) )
user_scopes = _user_scopes(sub) user_scopes = _user_scopes(user_id)
client_scopes = set(scope_to_list(grant.client.scope)) client_scopes = set(scope_to_list(grant.client.scope))
# Deny authorization if user lacks scopes requested by client # Deny authorization if user lacks scopes requested by client
@@ -49,7 +53,7 @@ def authorize():
response = server.create_authorization_response( response = server.create_authorization_response(
request=router.current_event, request=router.current_event,
grant_user=sub, grant_user=user_id,
grant=grant, grant=grant,
) )
except JoseError as err: except JoseError as err:
@@ -65,18 +69,16 @@ def authorize():
return response return response
def _user_scopes(sub: str) -> set: def _user_scopes(user_id: str) -> set:
return set( return OAUTH2_DEFAULT_SCOPES | set(
scope_to_list( scope_to_list(
dyn.collection.get_item( dyn.collection.get_item(
KeyPair( KeyPair(
pk=sub, pk=user_id,
sk=SortKey(sk='SCOPE', path_spec='scope'), sk=SortKey(sk='SCOPE', path_spec='scope'),
), ),
exc_cls=BadRequestError, raise_on_error=False,
default='',
) )
) )
) )
class InvalidSession(BadRequestError): ...

View File

@@ -88,7 +88,9 @@ def _create_user(*, user: User, password: str):
item={ item={
'sk': '0', 'sk': '0',
'email_verified': False, 'email_verified': False,
'created_at': now_, 'createdDate': now_,
# Post-migration (users): uncomment the folloing line
# 'created_at': now_,
} }
| asdict(user), | asdict(user),
) )
@@ -116,6 +118,7 @@ def _create_user(*, user: User, password: str):
# Post-migration (users): rename `cpf` to `CPF` # Post-migration (users): rename `cpf` to `CPF`
'id': 'cpf', 'id': 'cpf',
'sk': user.cpf, 'sk': user.cpf,
'user_id': user.id,
'created_at': now_, 'created_at': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
@@ -126,6 +129,7 @@ def _create_user(*, user: User, password: str):
# Post-migration (users): rename `email` to `EMAIL` # Post-migration (users): rename `email` to `EMAIL`
'id': 'email', 'id': 'email',
'sk': user.email, 'sk': user.email,
'user_id': user.id,
'created_at': now_, 'created_at': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',

View File

@@ -68,9 +68,9 @@ def test_forbidden(
method=HTTPMethod.GET, method=HTTPMethod.GET,
query_string_parameters={ query_string_parameters={
'response_type': 'code', 'response_type': 'code',
'client_id': '6ebe1709-0831-455c-84c0-d4c753bf33c6', 'client_id': '5e90c38f-f058-4e16-91fa-952554a290c5',
'redirect_uri': 'https://localhost/callback', 'redirect_uri': 'https://localhost/callback',
'scope': 'openid email offline_access', 'scope': 'apps:admin',
'nonce': '123', 'nonce': '123',
'state': '456', 'state': '456',
}, },
@@ -110,4 +110,4 @@ def test_invalid_session(
lambda_context, lambda_context,
) )
assert r['statusCode'] == HTTPStatus.BAD_REQUEST assert r['statusCode'] == HTTPStatus.NOT_FOUND

View File

@@ -3,6 +3,7 @@
{"id": "OAUTH2", "sk": "CLIENT_ID#8c5e92b0-9ed4-451e-8935-66084d8544b1", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "none"} {"id": "OAUTH2", "sk": "CLIENT_ID#8c5e92b0-9ed4-451e-8935-66084d8544b1", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 1", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "none"}
{"id": "OAUTH2", "sk": "CLIENT_ID#6ebe1709-0831-455c-84c0-d4c753bf33c6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 2", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access", "token_endpoint_auth_method": "none"} {"id": "OAUTH2", "sk": "CLIENT_ID#6ebe1709-0831-455c-84c0-d4c753bf33c6", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 2", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access", "token_endpoint_auth_method": "none"}
{"id": "OAUTH2", "sk": "CLIENT_ID#1db63660-063d-4280-b2ea-388aca4a9459", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 3", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "client_secret_basic"} {"id": "OAUTH2", "sk": "CLIENT_ID#1db63660-063d-4280-b2ea-388aca4a9459", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 3", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access apps:admin", "token_endpoint_auth_method": "client_secret_basic"}
{"id": "OAUTH2", "sk": "CLIENT_ID#5e90c38f-f058-4e16-91fa-952554a290c5", "client_secret": "1nFD8alDbGHgc3g1RLY960xyRJVee0SlMoIB0MUlSuiJy28W", "name": "pytest 2", "scope": "openid profile", "redirect_uris": ["https://localhost/callback"], "response_types": ["code"], "grant_types": ["authorization_code", "refresh_token"], "scope": "apps:studio", "token_endpoint_auth_method": "none"}
{"id": "OAUTH2#CODE", "sk": "CODE#kyqp3oSuRFTfuBaCmq3XOgGWg67l42Kt3D6xPEj7Yd3MLdi9", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "redirect_uri": "https://localhost/callback", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419", "nonce": null, "scope": "openid profile email apps:admins", "response_type": "code", "code_challenge": "ejYEIGKQUgMnNh4eV0sftb0hXdLwkvKm6OHXRYvC--I", "code_challenge_method": "S256", "created_at": "2025-08-07T12:38:26.550431-03:00"} {"id": "OAUTH2#CODE", "sk": "CODE#kyqp3oSuRFTfuBaCmq3XOgGWg67l42Kt3D6xPEj7Yd3MLdi9", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "redirect_uri": "https://localhost/callback", "user_id": "357db1c5-7442-4075-98a3-fbe5c938a419", "nonce": null, "scope": "openid profile email apps:admins", "response_type": "code", "code_challenge": "ejYEIGKQUgMnNh4eV0sftb0hXdLwkvKm6OHXRYvC--I", "code_challenge_method": "S256", "created_at": "2025-08-07T12:38:26.550431-03:00"}
{"id": "OAUTH2#TOKEN", "sk": "REFRESH_TOKEN#CyF3Ik3b9hMIo3REVv27gZAHd7dvwZq6QrkhWr7qHEen4UVy", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "token_type": "Bearer", "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6IlRjT0VuV3JGSUFEYlZJNjJlY1pzU28ydEI1eW5mbkZZNTZ0Uy05b0stNW8ifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiZXhwIjoxNzU5NTg2NzgzLCJjbGllbnRfaWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYiLCJpYXQiOjE3NTg5ODE5ODMsImp0aSI6Ik9uVzRIZm1FdFl2a21CbE4iLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIHJlYWQ6dXNlcnMiLCJzdWIiOiIzNTdkYjFjNS03NDQyLTQwNzUtOThhMy1mYmU1YzkzOGE0MTkiLCJhdWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYifQ.i0NVgvPuf5jvl8JcYNsVCzjVUTDLihgQO4LmLeNijx9Ed3p_EgtVtcHFWFvEebe_LwTuDDtIJveH22Piyp4zresNSc_YNumnuvoY1aNd0ic2RIEtXaklRroq0xHwL_IVT-Dt6P9xL5Hyygx47Pvmci4U3wWK32a6Sb1Mm7ZZgXA00xWI1bJ_zwxFLvDkHDp9nrAa_vEWN6zRBcWc7JYNsgiaPMC0DoL8it0k48_g44zfsjGAZLcWFMoPlYt3wIcQQDeCKMsSJI0VPnqKK0pq4OOVs-pjkMyAU5aEMPvVOwdAL3VZY16RXt3eTzsmMH1XoRdCMP6UAx4ZS10RLGUPeA", "scope": "openid profile email read:users", "user": {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "name": "S\u00e9rgio R Siqueira", "email": "sergio@somosbeta.com.br", "email_verified": false}, "expires_in": 180, "issued_at": 1758981984, "ttl": 1759586784} {"id": "OAUTH2#TOKEN", "sk": "REFRESH_TOKEN#CyF3Ik3b9hMIo3REVv27gZAHd7dvwZq6QrkhWr7qHEen4UVy", "client_id": "d72d4005-1fa7-4430-9754-80d5e2487bb6", "token_type": "Bearer", "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6IlRjT0VuV3JGSUFEYlZJNjJlY1pzU28ydEI1eW5mbkZZNTZ0Uy05b0stNW8ifQ.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwiZXhwIjoxNzU5NTg2NzgzLCJjbGllbnRfaWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYiLCJpYXQiOjE3NTg5ODE5ODMsImp0aSI6Ik9uVzRIZm1FdFl2a21CbE4iLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIHJlYWQ6dXNlcnMiLCJzdWIiOiIzNTdkYjFjNS03NDQyLTQwNzUtOThhMy1mYmU1YzkzOGE0MTkiLCJhdWQiOiJkNzJkNDAwNS0xZmE3LTQ0MzAtOTc1NC04MGQ1ZTI0ODdiYjYifQ.i0NVgvPuf5jvl8JcYNsVCzjVUTDLihgQO4LmLeNijx9Ed3p_EgtVtcHFWFvEebe_LwTuDDtIJveH22Piyp4zresNSc_YNumnuvoY1aNd0ic2RIEtXaklRroq0xHwL_IVT-Dt6P9xL5Hyygx47Pvmci4U3wWK32a6Sb1Mm7ZZgXA00xWI1bJ_zwxFLvDkHDp9nrAa_vEWN6zRBcWc7JYNsgiaPMC0DoL8it0k48_g44zfsjGAZLcWFMoPlYt3wIcQQDeCKMsSJI0VPnqKK0pq4OOVs-pjkMyAU5aEMPvVOwdAL3VZY16RXt3eTzsmMH1XoRdCMP6UAx4ZS10RLGUPeA", "scope": "openid profile email read:users", "user": {"id": "357db1c5-7442-4075-98a3-fbe5c938a419", "name": "S\u00e9rgio R Siqueira", "email": "sergio@somosbeta.com.br", "email_verified": false}, "expires_in": 180, "issued_at": 1758981984, "ttl": 1759586784}

View File

@@ -26,6 +26,7 @@ import {
} from './ui/dropdown-menu' } from './ui/dropdown-menu'
type NavItem = { type NavItem = {
appId: string
title: string title: string
url: string url: string
icon: LucideIcon icon: LucideIcon
@@ -34,23 +35,27 @@ type NavItem = {
const apps: NavItem[] = [ const apps: NavItem[] = [
{ {
appId: 'saladeaula',
title: 'Sala de aula', title: 'Sala de aula',
url: '//scorm.eduseg.workers.dev', url: '//scorm.eduseg.workers.dev',
icon: GraduationCapIcon icon: GraduationCapIcon
}, },
{ {
appId: 'admin',
title: 'Administrador', title: 'Administrador',
url: '//admin.saladeaula.digital', url: '//admin.saladeaula.digital',
icon: LayoutDashboardIcon, icon: LayoutDashboardIcon,
scope: ['apps:admin'] scope: ['apps:admin']
}, },
{ {
appId: 'studio',
title: 'EDUSEG® Estúdio', title: 'EDUSEG® Estúdio',
url: '//studio.saladeaula.digital', url: '//studio.saladeaula.digital',
icon: CirclePlayIcon, icon: CirclePlayIcon,
scope: ['apps:studio'] scope: ['apps:studio']
}, },
{ {
appId: 'insights',
title: 'EDUSEG® Insights', title: 'EDUSEG® Insights',
url: '//insights.saladeaula.digital', url: '//insights.saladeaula.digital',
icon: LightbulbIcon, icon: LightbulbIcon,
@@ -59,13 +64,15 @@ const apps: NavItem[] = [
] ]
export function NavUser({ export function NavUser({
user user,
excludeApps
}: { }: {
user: { user: {
name: string name: string
email: string email: string
scope: string scope: string
} }
excludeApps: string[]
}) { }) {
const userScope = user.scope.split(' ') const userScope = user.scope.split(' ')
@@ -135,10 +142,12 @@ export function NavUser({
</> </>
)} )}
{apps.map(({ title, url, scope = [], icon: Icon }, idx) => { {apps
.filter(({ appId }) => !excludeApps.includes(appId))
.map(({ appId, title, url, scope = [], icon: Icon }) => {
if (grantIfHas(scope, userScope)) { if (grantIfHas(scope, userScope)) {
return ( return (
<DropdownMenuItem key={idx} asChild> <DropdownMenuItem key={appId} asChild>
<Link to={url} className="cursor-pointer"> <Link to={url} className="cursor-pointer">
<Icon /> {title} <Icon /> {title}
</Link> </Link>

View File

@@ -1,6 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"declaration": true,
"declarationMap": true,
"strict": true, "strict": true,
"lib": ["DOM", "DOM.Iterable", "ES2022"], "lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["vite/client"], "types": ["vite/client"],

View File

@@ -132,9 +132,10 @@ def _create_user(rawuser: dict, context: dict) -> None:
'sk': '0', 'sk': '0',
'email_verified': False, 'email_verified': False,
'tenant_id': {org.id}, 'tenant_id': {org.id},
# Post-migration: uncomment the folloing line # Post-migration (users): uncomment the folloing line
# 'org_id': {org.id}, # 'org_id': {org.id},
'created_at': now_, # 'created_at': now_,
'createDate': now_,
}, },
) )
transact.put( transact.put(
@@ -147,7 +148,7 @@ def _create_user(rawuser: dict, context: dict) -> None:
transact.put( transact.put(
item={ item={
'id': user_id, 'id': user_id,
# Post-migration: rename `emails` to `EMAIL` # Post-migration (users): rename `emails` to `EMAIL`
'sk': f'emails#{user.email}', 'sk': f'emails#{user.email}',
'email_verified': False, 'email_verified': False,
'email_primary': True, 'email_primary': True,
@@ -169,9 +170,10 @@ def _create_user(rawuser: dict, context: dict) -> None:
) )
transact.put( transact.put(
item={ item={
# Post-migration: rename `cpf` to `CPF` # Post-migration (users): rename `cpf` to `CPF`
'id': 'cpf', 'id': 'cpf',
'sk': user.cpf, 'sk': user.cpf,
'user_id': user_id,
'created_at': now_, 'created_at': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
@@ -179,9 +181,10 @@ def _create_user(rawuser: dict, context: dict) -> None:
) )
transact.put( transact.put(
item={ item={
# Post-migration: rename `email` to `EMAIL` # Post-migration (users): rename `email` to `EMAIL`
'id': 'email', 'id': 'email',
'sk': user.email, 'sk': user.email,
'user_id': user_id,
'created_at': now_, 'created_at': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
@@ -191,7 +194,7 @@ def _create_user(rawuser: dict, context: dict) -> None:
item={ item={
'id': user_id, 'id': user_id,
'sk': f'orgs#{org.id}', 'sk': f'orgs#{org.id}',
# Post-migration: uncomment the following line # Post-migration (users): uncomment the following line
# pk=f'ORG#{org.id}', # pk=f'ORG#{org.id}',
'name': org.name, 'name': org.name,
'cnpj': org.cnpj, 'cnpj': org.cnpj,
@@ -201,7 +204,7 @@ def _create_user(rawuser: dict, context: dict) -> None:
transact.put( transact.put(
item={ item={
'id': f'orgmembers#{org.id}', 'id': f'orgmembers#{org.id}',
# Post-migration: uncomment the following line # Post-migration (users): uncomment the following line
# pk=f'MEMBER#ORG#{org_id}', # pk=f'MEMBER#ORG#{org_id}',
'sk': user_id, 'sk': user_id,
'created_at': now_, 'created_at': now_,