Skip to content
Snippets Groups Projects
Verified Commit 4301b688 authored by Andrej Ramašeuski's avatar Andrej Ramašeuski
Browse files

Zakladni podpora guest users

parent 84ecb967
Branches
No related tags found
No related merge requests found
Pipeline #11259 passed
...@@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y \ ...@@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y \
libcrypt-openssl-rsa-perl \ libcrypt-openssl-rsa-perl \
libcrypt-openssl-x509-perl \ libcrypt-openssl-x509-perl \
libdata-guid-perl \ libdata-guid-perl \
libdata-random-perl \
libdbix-class-perl \ libdbix-class-perl \
libdbd-pg-perl \ libdbd-pg-perl \
libdbi-perl \ libdbi-perl \
......
...@@ -127,7 +127,9 @@ sub startup( $self ) { ...@@ -127,7 +127,9 @@ sub startup( $self ) {
$r->get('/')->to(cb => sub { shift->render('index'); }); $r->get('/')->to(cb => sub { shift->render('index'); });
$r->get('/meets/:id')->requires(authenticated => 1)->to('Meets#meet'); $r->get('/meets/:id')->requires(authenticated => 1)->to('Meets#meet');
$r->get('/guest/:token')->to('Invites#meet');
$r->websocket('/ws')->to('Websockets#main');
} }
1; 1;
package SeMeet::Controller::Invites;
use Mojo::Base 'Mojolicious::Controller', -signatures;
use Data::Random qw(rand_chars);
sub create($c) {
$c->openapi->valid_input or return;
my $args = $c->req->json;
#### TODO: VALIDATE EMAIL
my $meet = $c->schema->resultset('Meet')->find({ id => $args->{meet_id} });
return $c->error(404, 'NOT_FOUND') if ! $meet;
my $roles = $meet->user_roles($c->stash->{user}, []);
if ( ! ($roles->{moderator} || $roles->{owner}) ) {
return $c->error(403, 'ACCESS_DENIED');
}
my $token = rand_chars( set => 'upperalpha', size => 8 );
my $guard = $c->schema->txn_scope_guard;
my $invite = $c->schema->resultset('Invite')->create({
token => $token,
user_id => $c->stash->{user}->id,
meet_id => $meet->id,
email => $args->{email},
displayname => $args->{displayname},
});
$guard->commit;
$invite = $c->schema->resultset('Invite')->find({
id => $invite->id
});
$c->trace(\'User %s create invite form meet "%s" with id %d',
$c->stash->{user}->username,
$meet->name,
$invite->id,
);
$c->render(
status => 201,
openapi => $c->spec_filter(
{ $invite->get_columns },
'Invite',
)
);
}
sub meet($c) {
my $invite = $c->schema->resultset('Invite')->search({
token => uc($c->stash->{token}),
expire => {'>' => \'now()'},
})->first;
if ( ! $invite ) {
# TODO: ochrana proti hledani
$c->render('invalid_invite');
return;
}
my $meet = $c->schema->resultset('Meet')->find({
id => $invite->meet_id,
});
return $c->error(404, 'NOT_FOUND') if ! $meet;
$c->stash->{meet} = $meet;
$c->stash->{token} = $invite->meet_token($meet, $c->config);
$c->render('meet_guest');
}
1;
...@@ -218,7 +218,6 @@ sub meet($c) { #NENI API! ...@@ -218,7 +218,6 @@ sub meet($c) { #NENI API!
my $roles = $meet->user_roles($user, $c->current_user->{groups}); my $roles = $meet->user_roles($user, $c->current_user->{groups});
$c->trace($roles); $c->trace($roles);
return $c->error(404, 'NOT_FOUND') if ! $roles->{any}; return $c->error(404, 'NOT_FOUND') if ! $roles->{any};
$c->stash->{meet} = $meet; $c->stash->{meet} = $meet;
......
...@@ -14,6 +14,8 @@ our %Lexicon = ( ...@@ -14,6 +14,8 @@ our %Lexicon = (
INPUT_MEET_ADD_GROUPS_PLACEHOLDER => 'Zadejte název skupiny pro hledání', INPUT_MEET_ADD_GROUPS_PLACEHOLDER => 'Zadejte název skupiny pro hledání',
INPUT_MEET_ADD_USERS_PLACEHOLDER => 'Zadejte jméno osoby pro hledání', INPUT_MEET_ADD_USERS_PLACEHOLDER => 'Zadejte jméno osoby pro hledání',
INPUT_MEET_ADD_GROUPS_LABEL => 'Přidat skupiny', INPUT_MEET_ADD_GROUPS_LABEL => 'Přidat skupiny',
INPUT_INVITE_DISPLAYNAME_PLACEHOLDER=> 'Jméno a příjmení zvané osoby',
INPUT_INVITE_EMAIL_PLACEHOLDER => 'Mailová adresa',
ERROR_SERVERSIDE => 'Chyba na straně serveru', ERROR_SERVERSIDE => 'Chyba na straně serveru',
ERROR_MEET_NAME_REQURED => "Název nové místnosti je povinný", ERROR_MEET_NAME_REQURED => "Název nové místnosti je povinný",
ERROR_MEET_NAME_DUPLICITY => 'Duplicitní název mistnosti', ERROR_MEET_NAME_DUPLICITY => 'Duplicitní název mistnosti',
...@@ -38,6 +40,8 @@ our %Lexicon = ( ...@@ -38,6 +40,8 @@ our %Lexicon = (
'Adding moderators' => 'Přidávání moderatorů', 'Adding moderators' => 'Přidávání moderatorů',
'Moderators' => 'Moderatoři', 'Moderators' => 'Moderatoři',
'Invites' => 'Pozvánky', 'Invites' => 'Pozvánky',
'Create invite' => 'Vytvořit pozvánku',
'Firstname, Lastname' => '',
); );
1; 1;
......
package SeMeet::Schema::Result::Invite;
use strict;
use warnings;
use base 'DBIx::Class::Core';
use Mojo::JWT;
use constant MEET_TOKEN_LIFETIME => 3600 * 24;
our $VERSION = 1;
__PACKAGE__->table('invites');
__PACKAGE__->add_columns(
id => {
data_type => 'integer',
is_auto_increment => 1,
is_nullable => 0,
sequence => 'uid_seq'
},
qw(
token
created
expire
user_id
meet_id
email
displayname
),
);
__PACKAGE__->set_primary_key('id');
__PACKAGE__->add_unique_constraint(
'token' => [qw(token)]
);
sub meet_token {
my $self = shift;
my $meet = shift;
my $cfg = shift;
return Mojo::JWT->new(
secret => $cfg->{jitsi_secret},
claims => {
aud => 'semeet',
iss => 'semeet',
sub => 'meet.pirati.cz',
room => $meet->uuid,
moderator => \0,
exp => time + MEET_TOKEN_LIFETIME,
context => {
user => {
name => $self->displayname . ' (HOST)',
email => $self->email,
}
},
}
)->encode;
}
1;
...@@ -81,6 +81,26 @@ components: ...@@ -81,6 +81,26 @@ components:
type: string type: string
nullable: true nullable: true
readOnly: true readOnly: true
Invite:
type: object
properties:
id:
type: integer
readOnly: true
token:
type: string
description: Access token
readOnly: true
expire:
type: string
description: Cas expirace
readOnly: true
displayname:
type: string
description: Jmeno, Prijmeni
email:
type: string
description: E-mail
paths: paths:
/meets: /meets:
...@@ -403,3 +423,41 @@ paths: ...@@ -403,3 +423,41 @@ paths:
responses: responses:
204: 204:
description: User deleted description: User deleted
/invites:
post:
x-mojo-to: invites#create
security:
- Bearer: []
tags:
- meets
summary: "Vytvorit pozvanku"
operationId: createInvite
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
displayname:
type: string
description: "Jmeno, Prijmeni"
example: "Jack Sparrow"
email:
type: string
description: E-mail
example: "brin@gmail.com"
meet_id:
type: integer
example: 666
required:
- meet_id
- displayname
- email
responses:
201:
description: Invite created
content:
application/json:
schema:
$ref: '#/components/schemas/Invite'
...@@ -90,3 +90,19 @@ alter table "users" add column "permissions" text; ...@@ -90,3 +90,19 @@ alter table "users" add column "permissions" text;
-- 8 up -- 8 up
alter table "groups" add column "deleted" timestamp(0); alter table "groups" add column "deleted" timestamp(0);
-- 9 up
create table "invites" (
"id" integer not null default nextval('uid_seq'),
"token" varchar(8) not null,
"created" timestamp(0) not null default now(),
"expire" timestamp(0) not null default now() + '10days',
"user_id" integer not null,
"meet_id" integer not null,
"email" text not null,
"displayname" text not null,
primary key("id"),
unique("token"),
foreign key ("meet_id") references "meets" ("id") on update cascade on delete cascade,
foreign key ("user_id") references "users" ("id") on update cascade on delete cascade
);
<form> <form>
<div class="form-field form-field--error form-field--required mb-4"> <div class="form-field form-field--required mb-4">
<label class="form-field__label" for="name"><%=l 'INPUT_MEET_NAME_LABEL' %></label> <label class="form-field__label" for="name"><%=l 'INPUT_MEET_NAME_LABEL' %></label>
<div class="form-field__wrapper form-field__wrapper--shadowed"> <div class="form-field__wrapper form-field__wrapper--shadowed">
<input type="text" class="text-input form-field__control form-field--required" value="" v-model="meet.name" id="name" /> <input type="text" class="text-input form-field__control form-field--required" value="" v-model="meet.name" id="name" />
......
% if ( $roles->{owner} || $roles->{moderator} ) {
<div class="grid grid-cols-5">
<div class="mr-2 col-span-2">
<label class="form-field__label"><%=l 'Jméno a příjmení' %></label>
<input type="text" class="w-full text-input" value="" v-model.lazy="newInvite.displayname" placeholder="<%=l 'INPUT_INVITE_DISPLAYNAME_PLACEHOLDER' %>"/>
</div>
<div class="mr-2 col-span-3">
<label class="form-field__label"><%=l 'E-mail' %></label>
<div class="flex flex-row">
<input type="text" class="w-full text-input mr-2" value="" v-model.lazy="newInvite.email" placeholder="<%=l 'INPUT_INVITE_EMAIL_PLACEHOLDER' %>"/>
<button class="btn btn--violet-400 btn--hoveractive text-sm" @click="addInvite()">
<div class="btn__body" style="white-space: nowrap;"><%=l 'Create invite' %></div>
</button>
</div>
</div>
</div>
% }
% layout 'default';
INVALID INVITE
...@@ -22,8 +22,9 @@ ...@@ -22,8 +22,9 @@
<div v-if="active_tab == 'moderators'"> <div v-if="active_tab == 'moderators'">
%= include 'includes/meet_moderators' %= include 'includes/meet_moderators'
</div> </div>
<div v-if="active_tab == 'invites'"> %# <div v-if="active_tab == 'invites'">
</div> %#= include 'includes/meet_invites'
%# </div>
<div v-if="active_tab == 'form'"> <div v-if="active_tab == 'form'">
% if ( $roles->{owner} ) { % if ( $roles->{owner} ) {
%= include 'includes/meet_form' %= include 'includes/meet_form'
...@@ -39,6 +40,7 @@ ...@@ -39,6 +40,7 @@
<script type="module"> <script type="module">
const GROUPS_URL = '/api/groups'; const GROUPS_URL = '/api/groups';
const USERS_URL = '/api/users'; const USERS_URL = '/api/users';
const INVITES_URL = '/api/invites';
const MEET_URL = '/api/meets/<%= stash->{id} %>'; const MEET_URL = '/api/meets/<%= stash->{id} %>';
const ADD_GROUPS_URL = '/api/meets/<%= stash->{id} %>/groups'; const ADD_GROUPS_URL = '/api/meets/<%= stash->{id} %>/groups';
const ADD_USERS_URL = '/api/meets/<%= stash->{id} %>/users'; const ADD_USERS_URL = '/api/meets/<%= stash->{id} %>/users';
...@@ -54,6 +56,8 @@ ...@@ -54,6 +56,8 @@
data: { data: {
meet: {}, meet: {},
newInvite: {},
active_tab: 'groups', active_tab: 'groups',
delete_confirm_visible: false, delete_confirm_visible: false,
...@@ -219,6 +223,39 @@ ...@@ -219,6 +223,39 @@
} }
}) })
}, },
addInvite: function() {
if ( this.newInvite.displayname == '' || this.newInvite.email == '' ) {
console.log('XXX');
return true;
}
this.newInvite.meet_id = <%= stash->{id} %>;
fetch(INVITES_URL, {
method: "POST",
headers: API_HEADERS,
body: JSON.stringify(this.newInvite),
})
.then((response) => {
if (response.ok) {
this.newInvite = {}
this.getMeet()
}
})
},
removeInvite: function(id) {
fetch(INVITES_URL + id, {
method: "DELETE",
headers: API_HEADERS,
})
.then((response) => {
if (response.ok) {
this.getMeet()
}
})
},
% } % }
} }
......
% layout 'default';
%= include 'includes/meet'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment