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: ` +
|
||||
allowed.map(v => quot(v)).join(', ')
|
||||
},
|
||||
'invalid_token': {
|
||||
status: 400,
|
||||
message: () => 'Invalid token'
|
||||
},
|
||||
// Things
|
||||
'disallowed_thing': {
|
||||
status: 400,
|
||||
@ -450,6 +454,18 @@ module.exports = class APIError {
|
||||
`The value for ${quot(key)} has the following errors: ` +
|
||||
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
|
||||
// TODO: specifying these errors here might be a violation
|
||||
|
@ -484,13 +484,15 @@ const v0_2 = async (req, res) => {
|
||||
});
|
||||
return svc_token.sign('share', {
|
||||
$: 'token:share',
|
||||
$v: 'v0.0.0',
|
||||
$v: '0.0.0',
|
||||
uid: share_uid,
|
||||
}, {
|
||||
expiresIn: '14d'
|
||||
});
|
||||
})();
|
||||
|
||||
const email_link = config.origin +
|
||||
`/sharelink?token=${share_token}`;
|
||||
const email_link =
|
||||
`${config.origin}?share_token=${share_token}`;
|
||||
|
||||
await svc_email.send_email({ email }, 'share_by_email', {
|
||||
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 { Actor, UserActorType } = require("./auth/Actor");
|
||||
const BaseService = require("./BaseService");
|
||||
@ -7,12 +11,143 @@ class ShareService extends BaseService {
|
||||
static MODULES = {
|
||||
uuidv4: require('uuid').v4,
|
||||
validator: require('validator'),
|
||||
express: require('express'),
|
||||
};
|
||||
|
||||
async _init () {
|
||||
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 ({
|
||||
issuer,
|
||||
email,
|
||||
|
Loading…
Reference in New Issue
Block a user