add fallback to id

This commit is contained in:
2025-12-03 13:31:28 -03:00
parent af45be7083
commit 1b4cbbce6c
8 changed files with 112 additions and 33 deletions

View File

@@ -121,6 +121,8 @@ def _create_user(user: User, org: Org) -> bool:
# 'org_id': {org.id}, # 'org_id': {org.id},
# 'created_at': now_, # 'created_at': now_,
'createDate': now_, 'createDate': now_,
# Makes the email searchable
'emails': {user.email},
}, },
) )
transact.put( transact.put(
@@ -137,8 +139,8 @@ def _create_user(user: User, org: Org) -> bool:
'sk': f'emails#{user.email}', 'sk': f'emails#{user.email}',
'email_verified': email_verified, 'email_verified': email_verified,
'email_primary': True, 'email_primary': True,
'mx_record_exists': email_verified,
'created_at': now_, 'created_at': now_,
**({'mx_record_exists': True} if email_verified else {}),
} }
) )

View File

@@ -20,13 +20,9 @@ router = Router()
dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
@router.get('/<user_id>/emails') class ConflictError(ServiceError):
def get_emails(user_id: str, start_key: Annotated[str | None, Query] = None): def __init__(self, msg: str | dict):
return dyn.collection.query( super().__init__(HTTPStatus.CONFLICT, msg)
# Post-migration (users): rename `emails` to `EMAIL`
key=KeyPair(user_id, 'emails'),
start_key=start_key,
)
class UserNotFoundError(NotFoundError): ... class UserNotFoundError(NotFoundError): ...
@@ -35,9 +31,19 @@ class UserNotFoundError(NotFoundError): ...
class EmailNotFoundError(NotFoundError): ... class EmailNotFoundError(NotFoundError): ...
class EmailConflictError(ServiceError): class EmailVerificationNotFoundError(NotFoundError): ...
def __init__(self, msg: str | dict):
super().__init__(HTTPStatus.CONFLICT, msg)
class EmailConflictError(ConflictError): ...
@router.get('/<user_id>/emails')
def get_emails(user_id: str, start_key: Annotated[str | None, Query] = None):
return dyn.collection.query(
# Post-migration (users): rename `emails` to `EMAIL`
key=KeyPair(user_id, 'emails'),
start_key=start_key,
)
@router.post('/<user_id>/emails') @router.post('/<user_id>/emails')
@@ -68,7 +74,6 @@ def add(
# Post-migration (users): rename `emails` to `EMAIL` # Post-migration (users): rename `emails` to `EMAIL`
'sk': f'emails#{email}', 'sk': f'emails#{email}',
'email_verified': False, 'email_verified': False,
'mx_record_exists': False,
'email_primary': False, 'email_primary': False,
'created_at': now_, 'created_at': now_,
} }
@@ -124,9 +129,6 @@ def request_verification(
return JSONResponse(status_code=HTTPStatus.NO_CONTENT) return JSONResponse(status_code=HTTPStatus.NO_CONTENT)
class EmailVerificationNotFoundError(NotFoundError): ...
@router.post('/<user_id>/emails/<code>/verify') @router.post('/<user_id>/emails/<code>/verify')
def verify(user_id: str, code: str): def verify(user_id: str, code: str):
r = dyn.collection.get_items( r = dyn.collection.get_items(

View File

@@ -20,6 +20,10 @@ export async function loader({ request, context }: Route.LoaderArgs) {
}) })
} }
if (!url.searchParams.has('client_id')) {
throw redirect('https://scorm.eduseg.workers.dev/')
}
const r = await fetch(issuerUrl.toString(), { const r = await fetch(issuerUrl.toString(), {
method: 'GET', method: 'GET',
headers: new Headers([ headers: new Headers([

View File

@@ -7,7 +7,10 @@ from aws_lambda_powertools.event_handler import (
Response, Response,
) )
from aws_lambda_powertools.event_handler.api_gateway import Router from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import ForbiddenError, NotFoundError from aws_lambda_powertools.event_handler.exceptions import (
NotFoundError,
UnauthorizedError,
)
from aws_lambda_powertools.event_handler.openapi.params import Body from aws_lambda_powertools.event_handler.openapi.params import Body
from aws_lambda_powertools.shared.cookies import Cookie from aws_lambda_powertools.shared.cookies import Cookie
from layercake.dateutils import now, ttl from layercake.dateutils import now, ttl
@@ -25,7 +28,7 @@ dyn = DynamoDBPersistenceLayer(OAUTH2_TABLE, dynamodb_client)
idp = boto3.client('cognito-idp') idp = boto3.client('cognito-idp')
class InvalidCredentialsError(ForbiddenError): ... class InvalidCredentialsError(UnauthorizedError): ...
class UserNotFoundError(NotFoundError): ... class UserNotFoundError(NotFoundError): ...
@@ -42,15 +45,21 @@ def authentication(
_get_idp_user(user_id, username, password) _get_idp_user(user_id, username, password)
else: else:
if not pbkdf2_sha256.verify(password, password_hash): if not pbkdf2_sha256.verify(password, password_hash):
raise InvalidCredentialsError('Invalid credentials') dyn.update_item(
key=KeyPair(user_id, 'FAILED_ATTEMPTS'),
update_expr='SET #count = if_not_exists(#count, :zero) + :one, \
updated_at = :now',
expr_attr_names={
'#count': 'failed_attempts',
},
expr_attr_values={
':zero': 0,
':one': 1,
':now': now(),
},
)
dyn.update_item( raise InvalidCredentialsError('Invalid credentials')
key=KeyPair(user_id, '0'),
# Post-migration (users): uncomment the following line
# update_expr='SET last_login = :now',
update_expr='SET lastLogin = :now',
expr_attr_values={':now': now()},
)
return Response( return Response(
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK,
@@ -146,6 +155,16 @@ def new_session(user_id: str) -> str:
exp = ttl(start_dt=now_, seconds=SESSION_EXPIRES_IN) exp = ttl(start_dt=now_, seconds=SESSION_EXPIRES_IN)
with dyn.transact_writer() as transact: with dyn.transact_writer() as transact:
transact.delete(key=KeyPair(user_id, 'FAILED_ATTEMPTS'))
transact.update(
key=KeyPair(user_id, '0'),
# Post-migration (users): uncomment the following line
# update_expr='SET last_login = :now',
update_expr='SET lastLogin = :now',
expr_attr_values={
':now': now_,
},
)
transact.put( transact.put(
item={ item={
'id': 'SESSION', 'id': 'SESSION',

View File

@@ -1,6 +1,6 @@
from http import HTTPMethod from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, PartitionKey
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
@@ -29,3 +29,31 @@ def test_authentication(
session = dynamodb_persistence_layer.collection.query(PartitionKey('SESSION')) session = dynamodb_persistence_layer.collection.query(PartitionKey('SESSION'))
# One seesion if created from seeds # One seesion if created from seeds
assert len(session['items']) == 2 assert len(session['items']) == 2
def test_invalid_password(
app,
seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = app.lambda_handler(
http_api_proxy(
raw_path='/authentication',
method=HTTPMethod.POST,
body={
'username': '07879819908',
'password': '123333',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.UNAUTHORIZED
failed = dynamodb_persistence_layer.collection.get_item(
KeyPair('357db1c5-7442-4075-98a3-fbe5c938a419', 'FAILED_ATTEMPTS')
)
assert 'failed_attempts' in failed

View File

@@ -32,7 +32,7 @@ Globals:
ENROLLMENT_TABLE: !Ref EnrollmentTable ENROLLMENT_TABLE: !Ref EnrollmentTable
COURSE_TABLE: !Ref CourseTable COURSE_TABLE: !Ref CourseTable
KONVIVA_API_URL: https://lms.saladeaula.digital KONVIVA_API_URL: https://lms.saladeaula.digital
KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}" KONVIVA_SECRET_KEY: '{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}'
Resources: Resources:
EventLog: EventLog:
@@ -49,7 +49,7 @@ Resources:
Type: AWS::Serverless::HttpApi Type: AWS::Serverless::HttpApi
Properties: Properties:
CorsConfiguration: CorsConfiguration:
AllowOrigins: ["*"] AllowOrigins: ['*']
AllowMethods: [POST, OPTIONS] AllowMethods: [POST, OPTIONS]
AllowHeaders: [Content-Type, X-Requested-With] AllowHeaders: [Content-Type, X-Requested-With]
@@ -90,12 +90,34 @@ Resources:
detail-type: [INSERT] detail-type: [INSERT]
detail: detail:
new_image: new_image:
sk: ["0"] sk: ['0']
cnpj: cnpj:
- exists: false - exists: false
metadata__konviva_user_id: metadata__konviva_user_id:
- exists: false - exists: false
EventCreateOrgFunction:
Type: AWS::Serverless::Function
Properties:
Handler: events.create_org.lambda_handler
LoggingConfig:
LogGroup: !Ref EventLog
Policies:
- DynamoDBWritePolicy:
TableName: !Ref UserTable
Events:
DynamoDBEvent:
Type: EventBridgeRule
Properties:
Pattern:
resources: [!Ref UserTable]
detail-type: [INSERT]
detail:
new_image:
sk: ['0']
cnpj:
- exists: true
EventUpdateUserFunction: EventUpdateUserFunction:
Type: AWS::Serverless::Function Type: AWS::Serverless::Function
Properties: Properties:
@@ -114,7 +136,7 @@ Resources:
detail-type: [MODIFY] detail-type: [MODIFY]
detail: detail:
new_image: new_image:
sk: ["0"] sk: ['0']
metadata__konviva_user_id: metadata__konviva_user_id:
- exists: true - exists: true
changes: [name, email, cpf] changes: [name, email, cpf]
@@ -159,7 +181,7 @@ Resources:
detail-type: [MODIFY] detail-type: [MODIFY]
detail: detail:
new_image: new_image:
sk: ["0"] sk: ['0']
status: [CANCELED] status: [CANCELED]
old_image: old_image:
status: [PENDING] status: [PENDING]

View File

@@ -20,7 +20,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
user_layer.update_item( user_layer.update_item(
key=KeyPair(new_image['sk'], '0'), key=KeyPair(new_image['sk'], '0'),
update_expr='ADD tenant_id :tenant_id SET updated_at = :updated_at', update_expr='ADD tenant_id :tenant_id \
SET updated_at = :updated_at',
expr_attr_values={ expr_attr_values={
':tenant_id': {tenant}, ':tenant_id': {tenant},
':updated_at': now_, ':updated_at': now_,

View File

@@ -242,6 +242,7 @@ Resources:
detail: detail:
new_image: new_image:
sk: sk:
# Post-migration (users): rename `emails` to `EMAIL`
- prefix: emails# - prefix: emails#
mx_record_exists: mx_record_exists:
- exists: false - exists: false