Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/auto approve reservation #588

Merged
merged 8 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
"database": "traction"
}
],
"python.analysis.extraPaths": ["./plugins", "./services"]
}
4 changes: 3 additions & 1 deletion charts/traction/templates/acapy_plugin_config_configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ data:
wallet_name: {{ .Values.acapy.pluginValues.tractionInnkeeper.walletName }}
print_key: {{ .Values.acapy.pluginValues.tractionInnkeeper.printKey }}
print_token: {{ .Values.acapy.pluginValues.tractionInnkeeper.printToken }}
reservation.expiry_minutes: {{ .Values.acapy.pluginValues.tractionInnkeeper.reservationExpiryMinutes }}
reservation:
expiry_minutes: {{ .Values.acapy.pluginValues.tractionInnkeeper.reservationExpiryMinutes }}
auto_approve: {{ .Values.acapy.pluginValues.tractionInnkeeper.reservationAutoApprove }}
1 change: 1 addition & 0 deletions charts/traction/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ acapy:
printKey: false
printToken: false
reservationExpiryMinutes: 2880
reservationAutoApprove: false


imagePullSecrets: []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ def default(cls):

class ReservationConfig(BaseModel):
expiry_minutes: int
auto_approve: bool

class Config:
alias_generator = _alias_generator
allow_population_by_field_name = True

@classmethod
def default(cls):
return cls(expiry_minutes=60)
return cls(expiry_minutes=60, auto_approve=False)


class TractionInnkeeperConfig(BaseModel):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,12 @@
import logging
import uuid

from aiohttp import web
from aiohttp_apispec import (
docs,
request_schema,
response_schema,
match_info_schema,
)
from aiohttp import ClientSession, web
from aiohttp_apispec import docs, match_info_schema, request_schema, response_schema
from aries_cloudagent.admin.request_context import AdminRequestContext
from aries_cloudagent.messaging.models.base import BaseModelError
from aries_cloudagent.messaging.models.openapi import OpenAPISchema
from aries_cloudagent.messaging.valid import (
UUIDFour,
JSONWebToken,
)
from aries_cloudagent.messaging.valid import JSONWebToken, UUIDFour
from aries_cloudagent.multitenant.admin.routes import (
CreateWalletTokenRequestSchema,
CreateWalletTokenResponseSchema,
Expand All @@ -28,9 +20,10 @@
from marshmallow import fields

from . import TenantManager
from .utils import approve_reservation, generate_reservation_token_data, ReservationException
from .models import (
ReservationRecordSchema,
ReservationRecord,
ReservationRecordSchema,
TenantRecord,
TenantRecordSchema,
)
Expand Down Expand Up @@ -238,6 +231,16 @@ async def tenant_reservation(request: web.BaseRequest):
await rec.save(session, reason="New tenant reservation")
LOGGER.info(rec)

if mgr._config.reservation.auto_approve:
LOGGER.info("Tenant auto-approve is on, approving newly created tenant")
try:
_pwd = await approve_reservation(rec.reservation_id, rec.state_notes, mgr)
return web.json_response({"reservation_id": rec.reservation_id, "reservation_pwd": _pwd})
except ReservationException as err:
raise web.HTTPConflict(
reason=str(err)
)

return web.json_response({"reservation_id": rec.reservation_id})


Expand All @@ -257,7 +260,9 @@ async def tenant_reservation_get(request: web.BaseRequest):
reservation_id = request.match_info["reservation_id"]

async with profile.session() as session:
rec = await ReservationRecord.retrieve_by_reservation_id(session, reservation_id)
rec = await ReservationRecord.retrieve_by_reservation_id(
session, reservation_id
)
LOGGER.info(rec)

return web.json_response(rec.serialize())
Expand All @@ -282,7 +287,9 @@ async def tenant_checkin(request: web.BaseRequest):
reservation_id = request.match_info["reservation_id"]

async with profile.session() as session:
res_rec = await ReservationRecord.retrieve_by_reservation_id(session, reservation_id)
res_rec = await ReservationRecord.retrieve_by_reservation_id(
session, reservation_id
)
reservation_pwd = body.get("reservation_pwd")

if res_rec.expired:
Expand Down Expand Up @@ -407,27 +414,14 @@ async def innkeeper_reservations_approve(request: web.BaseRequest):

# records are under base/root profile, use Tenant Manager profile
mgr = context.inject(TenantManager)
profile = mgr.profile

async with profile.session() as session:
# innkeeper can access all reservation records.
rec = await ReservationRecord.retrieve_by_reservation_id(
session, reservation_id, for_update=True
)
if rec.state == ReservationRecord.STATE_REQUESTED:
_pwd, _salt, _hash, _expiry = mgr.generate_reservation_token_data()
rec.reservation_token_salt = _salt.decode("utf-8")
rec.reservation_token_hash = _hash.decode("utf-8")
rec.reservation_token_expiry = _expiry
rec.state_notes = state_notes
rec.state = ReservationRecord.STATE_APPROVED
await rec.save(session)
LOGGER.info(rec)
else:
raise web.HTTPConflict(
reason=f"Reservation state is currently '{rec.state}' and cannot be set to '{ReservationRecord.STATE_APPROVED}'."
try:
_pwd = await approve_reservation(reservation_id, state_notes, mgr)
except ReservationException as err:
raise web.HTTPConflict(
reason=str(err)
)

return web.json_response({"reservation_pwd": _pwd})


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,22 +161,6 @@ async def create_innkeeper(self):
if config.print_token:
print(f"Bearer {token}\n")

def generate_reservation_token_data(self):
_pwd = str(uuid.uuid4().hex)
self._logger.info(f"_pwd = {_pwd}")

_salt = bcrypt.gensalt()
self._logger.info(f"_salt = {_salt}")

_hash = bcrypt.hashpw(_pwd.encode("utf-8"), _salt)
self._logger.info(f"_hash = {_hash}")

minutes = self._config.reservation.expiry_minutes
_expiry = datetime.utcnow() + timedelta(minutes=minutes)
self._logger.info(f"_expiry = {_expiry}")

return _pwd, _salt, _hash, _expiry

def check_reservation_password(
self, reservation_pwd: str, reservation: ReservationRecord
):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import bcrypt
import logging
import uuid
from datetime import datetime, timedelta

from aries_cloudagent.core.profile import Profile

from .models import (
ReservationRecord
)

from . import TenantManager

LOGGER = logging.getLogger(__name__)

def generate_reservation_token_data(expiry_minutes: int):
_pwd = str(uuid.uuid4().hex)
LOGGER.info(f"_pwd = {_pwd}")

_salt = bcrypt.gensalt()
LOGGER.info(f"_salt = {_salt}")

_hash = bcrypt.hashpw(_pwd.encode("utf-8"), _salt)
LOGGER.info(f"_hash = {_hash}")

minutes = expiry_minutes
_expiry = datetime.utcnow() + timedelta(minutes=minutes)
LOGGER.info(f"_expiry = {_expiry}")

return _pwd, _salt, _hash, _expiry

async def approve_reservation(reservation_id: str, state_notes: str, manager: TenantManager):
async with manager.profile.session() as session:
# find reservation records.
rec = await ReservationRecord.retrieve_by_reservation_id(
session, reservation_id, for_update=True
)
if rec.state == ReservationRecord.STATE_REQUESTED:
_pwd, _salt, _hash, _expiry = generate_reservation_token_data(manager._config.reservation.expiry_minutes)
rec.reservation_token_salt = _salt.decode("utf-8")
rec.reservation_token_hash = _hash.decode("utf-8")
rec.reservation_token_expiry = _expiry
rec.state_notes = state_notes
rec.state = ReservationRecord.STATE_APPROVED
await rec.save(session)
LOGGER.info(rec)
else:
raise ReservationException(
f"Reservation state is currently '{rec.state}' and cannot be set to '{ReservationRecord.STATE_APPROVED}'."
)

return _pwd

class ReservationException(Exception):
pass


3 changes: 2 additions & 1 deletion scripts/.env-example
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ ENDORSER_API_ADMIN_KEY=change-me
# Tenant UI Configuration
# ------------------------------------------------------------
# ------------------------------------------------------------

SERVER_SMTP_SERVER=maildev
SERVER_SMTP_PORT=1025
TENANT_UI_PORT=5101

# which traction api are we using?
Expand Down
12 changes: 10 additions & 2 deletions scripts/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
version: "3.9"
services:
ngrok-traction-agent:
image: wernight/ngrok
image: ngrok/ngrok
environment:
- TRACTION_ACAPY_HTTP_PORT=${TRACTION_ACAPY_HTTP_PORT}
ports:
- 4052:4040
command: ngrok http traction-agent:${TRACTION_ACAPY_HTTP_PORT} --log stdout
command: http traction-agent:${TRACTION_ACAPY_HTTP_PORT} --log stdout

traction-acapy-image-builder:
pull_policy: missing
Expand Down Expand Up @@ -132,6 +132,8 @@ services:
- UX_OWNER=${UX_OWNER}
- FRONTEND_ARIES_LEDGER_DESCRIPTION=${FRONTEND_ARIES_LEDGER_DESCRIPTION}
- FRONTEND_ACAPY_VERSION_DISPLAY=${FRONTEND_ACAPY_VERSION_DISPLAY}
- SERVER_SMTP_SERVER=maildev
- SERVER_SMTP_PORT=1025
ports:
- ${TENANT_UI_PORT}:8080
extra_hosts:
Expand Down Expand Up @@ -263,6 +265,12 @@ services:
retries: 5
extra_hosts:
- host.docker.internal:host-gateway

maildev:
image: maildev/maildev
ports:
- "1080:1080"
- "1025:1025"

volumes:
traction-wallet:
Expand Down
4 changes: 3 additions & 1 deletion scripts/plugin-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ traction_innkeeper:
wallet_key: change-me
print_key: true
print_token: true
reservation.expiry_minutes: 2880
reservation:
auto_approve: true
esune marked this conversation as resolved.
Show resolved Hide resolved
expiry_minutes: 2880
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
{{ $t('reservations.submitted') }} <br />
{{ $t('reservations.emailSentTo', [email]) }}
</p>
<p v-if="pwd" class="text-center">
{{ $t('reservations.passwordAvailable') }}
</p>
</template>
</Card>

Expand All @@ -29,6 +32,19 @@
/>
</div>
</div>

<div v-if="pwd" class="field w-full">
<label for="">{{ $t('reservations.reservationPassword') }}</label>
<div class="p-inputgroup">
<InputText :value="pwd" type="text" readonly class="w-full" />
<Button
icon="pi pi-copy"
title="Copy to clipboard"
class="p-button-secondary"
@click="copyResPwd"
/>
</div>
</div>
</template>

<script setup lang="ts">
Expand All @@ -42,10 +58,16 @@ const toast = useToast();
const props = defineProps<{
email: string;
id: string;
pwd: string;
}>();

const copyResId = () => {
navigator.clipboard.writeText(props.id);
toast.info('Copied Reservation Number to clipboard!');
};

const copyResPwd = () => {
navigator.clipboard.writeText(props.pwd);
toast.info('Copied Reservation Password to clipboard!');
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<ReservationConfirmation
:id="reservationIdResult"
:email="formFields.contact_email"
:pwd="reservationPwdResult"
/>
</div>

Expand Down Expand Up @@ -179,6 +180,7 @@ const { loading } = storeToRefs(useReservationStore());

// The reservation return object
const reservationIdResult: any = ref('');
const reservationPwdResult: any = ref('');

// Form submission
const submitted = ref(false);
Expand All @@ -191,6 +193,9 @@ const handleSubmit = async (isFormValid: boolean) => {
try {
const res = await reservationStore.makeReservation(formFields);
reservationIdResult.value = res.reservation_id;
reservationPwdResult.value = res.reservation_pwd
? res.reservation_pwd
: undefined;
} catch (err) {
console.error(err);
toast.error(`Failure making request: ${err}`);
Expand Down
2 changes: 2 additions & 0 deletions services/tenant-ui/frontend/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
"history": "History",
"incorrectEmailOrReservationId": "Incorrect Email or Reservation ID. Please try again.",
"otp": "The password is shown below one-time if you need to communicate it via other means",
"passwordAvailable": "The reservation password is visible below, please use it to complete your check-in.",
"passwordValid48Hours": "The reservation password is only valid for 48 hours from the time it was sent to your email address.",
"pending!": "PENDING!",
"requestDeclined": "We regret to inform you that your request has been declined.",
Expand All @@ -291,6 +292,7 @@
"requests": "Requests",
"reservationHistory": "Reservation History",
"reservationId": "Reservation ID",
"reservationPassword": "Reservation Password",
"reservationPreviouslyCompleted": "This reservation has already previously been completed and the wallet details given out.",
"reservations": "Reservations",
"reservationValidated": "Your reservation is validated successfully.",
Expand Down
4 changes: 3 additions & 1 deletion services/tenant-ui/frontend/src/plugins/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
"history": "History <FR>",
"incorrectEmailOrReservationId": "Incorrect Email or Reservation ID. Please try again. <FR>",
"otp": "The password is shown below one-time if you need to communicate it via other means <FR>",
"passwordAvailable": "The reservation password is visible below, please use it to complete your check-in. <FR>",
"passwordValid48Hours": "The reservation password is only valid for 48 hours from the time it was sent to your email address. <FR>",
"pending!": "PENDING! <FR>",
"requestDeclined": "We regret to inform you that your request has been declined. <FR>",
Expand All @@ -291,6 +292,7 @@
"requests": "Requests <FR>",
"reservationHistory": "Reservation History <FR>",
"reservationId": "Reservation ID <FR>",
"reservationPassword": "Reservation Password <FR>",
"reservationPreviouslyCompleted": "This reservation has already previously been completed and the wallet details given out. <FR>",
"reservations": "Reservations <FR>",
"reservationValidated": "Your reservation is validated successfully. <FR>",
Expand Down Expand Up @@ -384,4 +386,4 @@
"verification": "Verification <FR>",
"verifications": "Verifications <FR>"
}
}
}
Loading