Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
232 lines (196 sloc) 11 KB
# Sandstorm - Personal Cloud Sandbox
# Copyright (c) 2014 Sandstorm Development Group, Inc. and contributors
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This is used specifically with hack-session.capnp.
# It is subject to change after the Powerbox functionality is implemented.
@0xdd10df585a82c6d8;
$import "/capnp/c++.capnp".namespace("sandstorm");
using Grain = import "grain.capnp";
using Util = import "util.capnp";
struct EmailAddress {
# Email addresses are (usually) of the form "Full Name <example@example.org>".
# This struct separates the display name and address into distinct fields.
address @0 :Text;
name @1 :Text;
# TODO(someday): We could add a field `token` which is a capability representing "email capital",
# which you can think of like "political capital", but specifically representing some social
# permission to send email to this address. A token can be used some small number of times to
# send email to a particular address. Tokens can be requested using the inline powerbox: if a
# human user types (or selects from autocomplete) an address, this is good enough to allow the
# app to send a few messages to that address. Tokens would also be received as part of incoming
# messages, representing permission to reply (and reply-all).
}
struct EmailAttachment {
contentType @0 :Text; # header is actually content-type
contentDisposition @1 :Text; # header is actually content-disposition
contentId @2 :Text; # header is actually content-id
content @3 :Data;
}
struct EmailMessage {
date @0 :Int64; # Nanoseconds since unix epoch.
from @1 :EmailAddress;
to @2 :List(EmailAddress);
cc @3 :List(EmailAddress);
bcc @4 :List(EmailAddress);
replyTo @5 :EmailAddress; # header is actually reply-to
# TODO(someday): replyTo should actually be a List(EmailAddress)
messageId @6 :Text; # header is actually message-id
references @7 :List(Text);
inReplyTo @8 :List(Text); # header is actually in-reply-to
# TODO(someday): Add list-id.
subject @9 :Text;
# Separate body into text and html fields.
# Any other content-types will be in the attachments field.
text @10 :Text;
html @11 :Text;
attachments @12 :List(EmailAttachment);
}
interface EmailSendPort @0xec831dbf4cc9bcca {
# Represents a destination for e-mail.
#
# Make a Powerbox request for `EmailSendPort` when you want to request permission to send email
# to the current user. For example, a mailing list app would request this when a user indicates
# they wish to subscribe to the list.
#
# Make a Powerbox offer of an `EmailSendPort` when you want to receive email on that port.
# The user will be prompted to assign your port to an address. For example, a mailing list app
# would offer an `EmailSendPort` to the owner during initial setup.
send @0 (email :EmailMessage);
# Send a message through this port.
hintAddress @1 (address :EmailAddress);
# Hint to the port that it is now receiving email sent to the given address. The email driver
# will call this after the port has been bound to an address after being offered through the
# Powerbox. The purpose is to allow the app to display back to the user what address it is
# bound to. Implementing this is optional.
struct PowerboxTag {
# Tag which can be set on `PowerboxDescriptor`s when requesting an `EmailSendPort`.
fromHint @0 :EmailAddress;
# The user will be offered the ability to override the "from" address on emails sent through
# this port. The application requesting the port may provide `fromHint` to suggest an override
# address to the user. Do not provide a `fromHint` if you do not want `from` addresses to be
# overridden, but note that the user may choose to do so anyway.
#
# It does not make sense to fill in this field when making a Powerbox offer.
listIdHint @1 :Text;
# The user will be offered the ability to set the "list-id" on emails sent through this port.
# The application requesting the port may provide 'listIdHint' to suggest a list ID to use.
# Do not provide a `listIdHint` if you do not want the list ID to be set, but note that the
# user may choose to set it anyway.
#
# It does not make sense to fill in this field when making a Powerbox offer.
}
}
interface VerifiedEmail @0xf88bf102464dfa5a {
# By default, an app is not told a user's email address. You may, however, request that the user
# provide their verified email address through the powerbox.
#
# First, you need an `EmailVerifier`. See that class for info on how to get one.
#
# To verify a user, make a powerbox request for `VerifiedEmail`. In your `PowerboxDescriptor`,
# set the tag value to a `VerifiedEmail.PowerboxTag` which has `authority` set to the
# capability returned by your `EmailVerifier`'s `getAuthority()` method. The user will be asked to
# choose one of their addresses. The Powerbox returns an `VerifiedEmail` for the address they
# chose. You must then pass this object to `EmailVerifier.verifyEmail()` to verify that
# the capability really is attached to the user's account and obtain the final address.
#
# You might wonder why the `EmailVerifier.verifyEmail()` step is necessary -- why not just
# have a `getAddress()` method on `VerifiedEmail` itself? The answer is that although the
# powerbox will only offer the user a choice of addresses associated with their account, the
# powerbox code runs client-side, and thus a malicious user could inject an arbitrary capability
# in place of the powerbox's response. So, they could respond with a fake `VerifiedEmail`
# capability.
struct PowerboxTag {
# Use this type as the tag value when requesting an VerifiedEmail in order to narrow the
# choices presented to the user.
verifierId @0 :Data;
# Value returned by `EmailVerifier.getId()`. Pass this to ensure that only
# addresses which can be verified by this verifier are offered as options.
address @1 :Text;
# Specify a complete address (like "foo@example.com") in a Powerbox request to request that
# the user verify this specific address and no other. The user will still have the choice to
# refuse verification, but will not be offered other addresses.
#
# This field will NOT be present in the tag returned with the powerbox response, because the
# field could be spoofed by the user. You must invoke `EmailVerifier.verifyEmail()` to find out
# the matching address.
domain @2 :Text;
# Specify a domain (like "example.com") in a Powerbox request to require that the user choose
# an address under the specified domain. The user will still have the choice to refuse
# verification, but will not be offered addresses under other domains.
#
# This field will NOT be present in the tag returned with the powerbox response, because the
# field could be spoofed by the user. You must invoke `EmailVerifier.verifyEmail()` to find out
# the matching address.
}
}
interface VerifiedEmailSendPort @0xa3cc885445aed8e9 extends(VerifiedEmail, EmailSendPort) {
# Make a PowerboxRequest for this type when you want to both verify an email and request
# the ability to send messages to it.
struct PowerboxTag {
verification @0 :VerifiedEmail.PowerboxTag;
port @1 :EmailSendPort.PowerboxTag;
}
}
interface EmailVerifier @0xd458f7ca9d1ba9ff {
# Object which can verify users' email addresses.
#
# To obtain an `EmailVerifier`, do a powerbox request for one, usually during first-time
# setup of your app. The user will be asked what kinds of address verification mechanisms they
# wish to trust -- e.g., do they trust that if Github says it has verified an address, it really
# has, or do they want Sandstorm do directly verify addresses? SECURITY NOTE: The user from whom
# you request the `EmailVerifier` will have the ability to spoof verifications, so only
# request it from the grain owner!
#
# Once you have a verifier, you can verify a user's address by making a powerbox request for
# an `VerifiedEmail` from that user; see the docs for `VerifiedEmail` for info.
getId @0 () -> (id :Data);
# Place the returned value in your `VerifiedEmail.PowerboxTag` when making a powerbox
# request. This tells the powerbox to filter for options that will be accepted by this
# `EmailVerifier`.
#
# Implementations of EmailVerifier should generate this value randomly to prevent collisions,
# but note that the ID need not be kept secret, since it's really only a filtering hint.
verifyEmail @1 (tabId :Data, verification :VerifiedEmail) -> (address :Text);
# Unpack the given verification to read the address that was verified.
#
# `tabId` comes from `UiView.newSession()`; verification only succeeds if it was in fact this
# tab's user whose address was verified. This exists to prevent the following MITM attack: Alice
# wants to falsely verify to a grain belonging to Carol that she owns Bob's address. So, she
# creates a grain of her own running an app that requests email verification and sends it to Bob.
# Bob willingly verifies his email to Alice's grain since Alice already knows his address anyway.
# However, Alice's grain actually stashes the VerifiedEmail capability. Alice then visits
# Carol's grain which requests email verification. Alice directs her malicious grain to respond
# to the powerbox request with Bob's VerifiedEmail. This doesn't work because Bob's
# VerifiedEmail was created in Bob's tab (against Alice's grain), and therefore when Carol's
# grain tries to verify it passing Alice's `tabId`, they don't match, and the verification fails.
}
interface EmailAgent @0x8b6f158d70cbc773 {
# Represents the ability to send and receive email as some user.
#
# Make a Powerbox request for `EmailAgent` when you want to request the ability to send and
# receive email under some address. For example, an email client app would request this.
send @0 (email :EmailMessage);
# Send a message from this user to all the addresses listed in the message's `to`, `cc`, and
# `bcc` fields. `email.from` is ignored; it will be replaced with the address associated with
# this capability. `email.bcc` will not be revealed to recipients.
addReceiver @1 (port :EmailSendPort) -> (handle :Util.Handle);
# Arrange for future mail sent to this user to be delivered to `port`.
#
# Drop the returned handle to unsubscribe. This handle is persistent (can be saved as a
# SturdyRef).
#
# Multiple `port`s can be added; each will receive a copy.
}
# TODO(someday): Support remote mailboxes, e.g. IMAP. Look at Nylas API for design hints!
You can’t perform that action at this time.