# 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 you `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 requiure 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!
