mirror of
https://github.com/HeyPuter/puter
synced 2024-11-14 22:06:00 +00:00
feat: add endpoints for share tokens
This commit is contained in:
parent
db5990a989
commit
301ffaf61d
@ -47,6 +47,10 @@ module.exports = class APIError {
|
|||||||
`value of ${quot(key)} must be one of: ` +
|
`value of ${quot(key)} must be one of: ` +
|
||||||
allowed.map(v => quot(v)).join(', ')
|
allowed.map(v => quot(v)).join(', ')
|
||||||
},
|
},
|
||||||
|
'invalid_token': {
|
||||||
|
status: 400,
|
||||||
|
message: () => 'Invalid token'
|
||||||
|
},
|
||||||
// Things
|
// Things
|
||||||
'disallowed_thing': {
|
'disallowed_thing': {
|
||||||
status: 400,
|
status: 400,
|
||||||
@ -450,6 +454,18 @@ module.exports = class APIError {
|
|||||||
`The value for ${quot(key)} has the following errors: ` +
|
`The value for ${quot(key)} has the following errors: ` +
|
||||||
errors.join('; ')
|
errors.join('; ')
|
||||||
},
|
},
|
||||||
|
'share_expired': {
|
||||||
|
status: 422,
|
||||||
|
message: 'This share is expired.'
|
||||||
|
},
|
||||||
|
'email_must_be_confirmed': {
|
||||||
|
status: 422,
|
||||||
|
message: 'Email must be confirmed to apply a share.',
|
||||||
|
},
|
||||||
|
'can_not_apply_to_this_user': {
|
||||||
|
status: 422,
|
||||||
|
message: 'This share can not be applied to this user.',
|
||||||
|
},
|
||||||
|
|
||||||
// Chat
|
// Chat
|
||||||
// TODO: specifying these errors here might be a violation
|
// TODO: specifying these errors here might be a violation
|
||||||
|
@ -484,13 +484,15 @@ const v0_2 = async (req, res) => {
|
|||||||
});
|
});
|
||||||
return svc_token.sign('share', {
|
return svc_token.sign('share', {
|
||||||
$: 'token:share',
|
$: 'token:share',
|
||||||
$v: 'v0.0.0',
|
$v: '0.0.0',
|
||||||
uid: share_uid,
|
uid: share_uid,
|
||||||
|
}, {
|
||||||
|
expiresIn: '14d'
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const email_link = config.origin +
|
const email_link =
|
||||||
`/sharelink?token=${share_token}`;
|
`${config.origin}?share_token=${share_token}`;
|
||||||
|
|
||||||
await svc_email.send_email({ email }, 'share_by_email', {
|
await svc_email.send_email({ email }, 'share_by_email', {
|
||||||
link: email_link,
|
link: email_link,
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
const APIError = require("../api/APIError");
|
||||||
|
const { get_user } = require("../helpers");
|
||||||
|
const configurable_auth = require("../middleware/configurable_auth");
|
||||||
|
const { Endpoint } = require("../util/expressutil");
|
||||||
const { whatis } = require("../util/langutil");
|
const { whatis } = require("../util/langutil");
|
||||||
const { Actor, UserActorType } = require("./auth/Actor");
|
const { Actor, UserActorType } = require("./auth/Actor");
|
||||||
const BaseService = require("./BaseService");
|
const BaseService = require("./BaseService");
|
||||||
@ -7,12 +11,143 @@ class ShareService extends BaseService {
|
|||||||
static MODULES = {
|
static MODULES = {
|
||||||
uuidv4: require('uuid').v4,
|
uuidv4: require('uuid').v4,
|
||||||
validator: require('validator'),
|
validator: require('validator'),
|
||||||
|
express: require('express'),
|
||||||
};
|
};
|
||||||
|
|
||||||
async _init () {
|
async _init () {
|
||||||
this.db = await this.services.get('database').get(DB_WRITE, 'share');
|
this.db = await this.services.get('database').get(DB_WRITE, 'share');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
['__on_install.routes'] (_, { app }) {
|
||||||
|
// track: scoping iife
|
||||||
|
const router = (() => {
|
||||||
|
const require = this.require;
|
||||||
|
const express = require('express');
|
||||||
|
return express.Router();
|
||||||
|
})();
|
||||||
|
|
||||||
|
app.use('/sharelink', router);
|
||||||
|
|
||||||
|
const svc_share = this.services.get('share');
|
||||||
|
const svc_token = this.services.get('token');
|
||||||
|
|
||||||
|
Endpoint({
|
||||||
|
route: '/check',
|
||||||
|
methods: ['POST'],
|
||||||
|
handler: async (req, res) => {
|
||||||
|
// Potentially confusing:
|
||||||
|
// The "share token" and "share cookie token" are different!
|
||||||
|
// -> "share token" is from the email link;
|
||||||
|
// it has a longer expiry time and can be used again
|
||||||
|
// if the share session expires.
|
||||||
|
// -> "share cookie token" lets the backend know it
|
||||||
|
// should grant permissions when the correct user
|
||||||
|
// is logged in.
|
||||||
|
|
||||||
|
const share_token = req.body.token;
|
||||||
|
|
||||||
|
if ( ! share_token ) {
|
||||||
|
throw APIError.create('field_missing', null, {
|
||||||
|
key: 'token',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoded = await svc_token.verify('share', share_token);
|
||||||
|
console.log('decoded?', decoded);
|
||||||
|
if ( decoded.$ !== 'token:share' ) {
|
||||||
|
throw APIError.create('invalid_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
const share = await svc_share.get_share({
|
||||||
|
uid: decoded.uid,
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( ! share ) {
|
||||||
|
throw APIError.create('invalid_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
$: 'api:share',
|
||||||
|
uid: share.uid,
|
||||||
|
email: share.recipient_email,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}).attach(router);
|
||||||
|
|
||||||
|
Endpoint({
|
||||||
|
route: '/apply',
|
||||||
|
methods: ['POST'],
|
||||||
|
mw: [configurable_auth()],
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const share_uid = req.body.uid;
|
||||||
|
|
||||||
|
const share = await svc_share.get_share({
|
||||||
|
uid: share_uid,
|
||||||
|
});
|
||||||
|
|
||||||
|
share.data = this.db.case({
|
||||||
|
mysql: () => share.data,
|
||||||
|
otherwise: () =>
|
||||||
|
JSON.parse(share.data ?? '{}'),
|
||||||
|
})();
|
||||||
|
|
||||||
|
if ( ! share ) {
|
||||||
|
throw APIError.create('share_expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
const actor = Actor.adapt(req.actor ?? req.user);
|
||||||
|
if ( ! actor ) {
|
||||||
|
// this shouldn't happen; auth should catch it
|
||||||
|
throw new Error('actor missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! actor.type.user.email_confirmed ) {
|
||||||
|
throw APIError.create('email_must_be_confirmed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( actor.type.user.email !== share.recipient_email ) {
|
||||||
|
throw APIError.create('can_not_apply_to_this_user');
|
||||||
|
}
|
||||||
|
|
||||||
|
const issuer_user = await get_user({
|
||||||
|
id: share.issuer_user_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( ! issuer_user ) {
|
||||||
|
throw APIError.create('share_expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
const issuer_actor = await Actor.create(UserActorType, {
|
||||||
|
user: issuer_user,
|
||||||
|
});
|
||||||
|
|
||||||
|
const svc_permission = this.services.get('permission');
|
||||||
|
|
||||||
|
for ( const permission of share.data.permissions ) {
|
||||||
|
await svc_permission.grant_user_user_permission(
|
||||||
|
issuer_actor,
|
||||||
|
actor.type.user.username,
|
||||||
|
permission,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
$: 'api:status-report',
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).attach(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_share ({ uid }) {
|
||||||
|
const [share] = await this.db.read(
|
||||||
|
'SELECT * FROM share WHERE uid = ?',
|
||||||
|
[uid],
|
||||||
|
);
|
||||||
|
|
||||||
|
return share;
|
||||||
|
}
|
||||||
|
|
||||||
async create_share ({
|
async create_share ({
|
||||||
issuer,
|
issuer,
|
||||||
email,
|
email,
|
||||||
|
Loading…
Reference in New Issue
Block a user