Here are the fields stored on the server for each device a user adds.
{
// The user this device belongs to
"uid": "4c352927cd4f4a4aa03d7d1893d950b8",
// The active session for this device
"sessionToken": "27cd4f4a4aa03d7d186a2ec81cbf19d5c8a604713362df9ee15c4f4a4aa03d7d",
// A server assigned identifier for this device
"id":"01ab",
// A human identifier for this devices. Able to be updated by the user
"name": "My Phone",
// General type of device. Useful for device list icons and demographic metrics
"type": "mobile",
// Timestamp of when the device was first created, à la Date.now()
"createdAt": 1442785364807,
// The push notification endpoint for this device/user
"callback": "https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef"
}
curl -v \
-X POST \
-H "Content-Type: application/json" \
https://api-accounts.dev.lcip.org/v1/account/login?keys=true \
-d '{
"email": "[email protected]",
"authPW": "996bc6b1aa63cd69856a2ec81cbf19d5c8a604713362df9ee15c2bf07128efab",
"device": {
"name": "My Phone",
"type": "mobile",
"callback": "https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef"
}
}'
curl -v \
-X POST \
-H "Content-Type: application/json" \
https://api-accounts.dev.lcip.org/v1/account/login?keys=true \
-d '{
"email": "[email protected]",
"authPW": "996bc6b1aa63cd69856a2ec81cbf19d5c8a604713362df9ee15c2bf07128efab",
"device": {
"id": "3d7d"
}
}'
{
"uid": "4c352927cd4f4a4aa03d7d1893d950b8",
"sessionToken": "27cd4f4a4aa03d7d186a2ec81cbf19d5c8a604713362df9ee15c4f4a4aa03d7d",
"keyFetchToken": "7d1893d950b8cd69856a2ec81cbfd7d1893d950b3362df9e56a2ec81cbf19d5c",
"verified": true,
"authAt": 1392144866,
"device": {
"id": "3d7d",
"name": "My Phone",
"type": "mobile",
"callback": "https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef"
}
}
curl -v \
-X POST \
-H "Content-Type: application/json" \
"https://api-accounts.dev.lcip.org/v1/account/create?keys=true" \
-d '{
"email": "[email protected]",
"authPW": "996bc6b1aa63cd69856a2ec81cbf19d5c8a604713362df9ee15c2bf07128efab",
"device": {
"name": "My Phone",
"type": "mobile",
"callback": "https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef"
}
}'
If a device id exists for this device, the request may include it, but it must also include the other info as well since device ids are not globally unique.
curl -v \
-X POST \
-H "Content-Type: application/json" \
"https://api-accounts.dev.lcip.org/v1/account/create?keys=true" \
-d '{
"email": "[email protected]",
"authPW": "996bc6b1aa63cd69856a2ec81cbf19d5c8a604713362df9ee15c2bf07128efab",
"device": {
"id": "3d7d",
"name": "My Phone",
"type": "mobile",
"callback": "https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef"
}
}'
{
"uid": "4c352927cd4f4a4aa03d7d1893d950b8",
"sessionToken": "27cd4f4a4aa03d7d186a2ec81cbf19d5c8a604713362df9ee15c4f4a4aa03d7d",
"keyFetchToken": "7d1893d950b8cd69856a2ec81cbfd7d1893d950b3362df9e56a2ec81cbf19d5c",
"authAt": 1392144866,
"device": {
"id": "3d7d",
"name": "My Phone",
"type": "mobile",
"callback": "https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef"
}
}
Authenticated with session token or oauth
curl -v \
-X POST \
-H "Host: api-accounts.dev.lcip.org" \
-H "Content-Type: application/json" \
-H 'Authorization: Hawk id="d4c5b1e3f5791ef83896c27519979b93a45e6d0da34c7509c5632ac35b28b48d", ts="1373391043", nonce="ohQjqb", hash="vBODPWhDhiRWM4tmI9qp+np+3aoqEFzdGuGk0h7bh9w=", mac="LAnpP3P2PXelC6hUoUaHP72nCqY5Iibaa3eeiGBqIIU="' \
https://api-accounts.dev.lcip.org/v1/account/device \
-d '{
"name": "My Phone",
"type": "mobile",
"callback": "https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef"
}'
curl -v \
-X POST \
-H "Host: api-accounts.dev.lcip.org" \
-H "Content-Type: application/json" \
-H 'Authorization: Hawk id="d4c5b1e3f5791ef83896c27519979b93a45e6d0da34c7509c5632ac35b28b48d", ts="1373391043", nonce="ohQjqb", hash="vBODPWhDhiRWM4tmI9qp+np+3aoqEFzdGuGk0h7bh9w=", mac="LAnpP3P2PXelC6hUoUaHP72nCqY5Iibaa3eeiGBqIIU="' \
https://api-accounts.dev.lcip.org/v1/account/device \
-d '{
"id": "3d7d",
"name": "My Old Phone"
}'
{
"id": "3d7d",
"name": "My Phone",
"type": "mobile",
"callback": "https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef"
}
Authenticated with session token or oauth
curl -v \
-X POST \
-H "Host: api-accounts.dev.lcip.org" \
-H "Content-Type: application/json" \
-H 'Authorization: Hawk id="d4c5b1e3f5791ef83896c27519979b93a45e6d0da34c7509c5632ac35b28b48d", ts="1373391043", nonce="ohQjqb", hash="vBODPWhDhiRWM4tmI9qp+np+3aoqEFzdGuGk0h7bh9w=", mac="LAnpP3P2PXelC6hUoUaHP72nCqY5Iibaa3eeiGBqIIU="' \
https://api-accounts.dev.lcip.org/v1/account/device/destroy \
-d '{
"id": "3d7d"
}'
{}
Authenticated with session token or oauth
curl -v \
-X GET \
-H "Host: api-accounts.dev.lcip.org" \
-H "Content-Type: application/json" \
-H 'Authorization: Hawk id="d4c5b1e3f5791ef83896c27519979b93a45e6d0da34c7509c5632ac35b28b48d", ts="1373391043", nonce="ohQjqb", hash="vBODPWhDhiRWM4tmI9qp+np+3aoqEFzdGuGk0h7bh9w=", mac="LAnpP3P2PXelC6hUoUaHP72nCqY5Iibaa3eeiGBqIIU="' \
https://api-accounts.dev.lcip.org/v1/account/devices \
Instead of including the sessionToken
field, each device will have a status of "connected" or "disconnected".
[
{
"id": "3d7d",
"name": "My Phone",
"type": "mobile",
"status": "connected",
"callback": "https://updates.push.services.mozilla.com/update/abcdef01234567890abcdefabcdef01234567890abcdef"
},
{
"id": "01ab",
"name": "My Desktop",
"type": "desktop",
"status": "disconnected",
"callback": "https://updates.push.services.mozilla.com/update/d4c5b1e3f5791ef83896c27519979b93a45e6d0da34c75"
}
]
A recovering device is one that has been registered before but has lost its device id. We may rely on the device "name" field in this case. The recovering device will login as a new device, submitting its name, if the name matches an existing device we will consider them to be the same, otherwise a new device will be created.
A shared device may or may not use the same device id for each user (there are tradeoffs for both) but either way it should be difficult for the service to detect if a device is shared and by whom.
If a login request specifies an unknown device id the login may still succeed, however the device field in the response will be null
. This may indicate that the device was explicitly deleted from the device list by the user. A POST to /v1/account/device
can attach a new device to the current session.
- How big should the device id be?
- large enough to make collisions unlikely within an individual account
- small enough that a device id alone can't distinguish between hundreds of accounts
- A 2-byte id seems to be small enough. Too small?
- For shared devices is device info more closely bound to the account or device?
- Example: Alice and Bob share a device. Alice registers the device with the name "Alice's Tablet". Later Bob logs in and changes the name to "Bob's Tablet". When Alice logs in again, which name should she see?
- The server still has her device name as "Alice's Tablet", but the device may have saved it locally as "Bob's Tablet". Should Alice's record be updated?
- It can work either way, but we should decide on one or the other.
- shared device, shared info
- or shared device, individual info
- the push callback leads me to lean toward individual info
It's not really clear how this should behave from device's individual perspective, let alone when you put the server in the mix. For e.g. a desktop firefox instance, ISTM that "Bob logs in" implies that Alice gets logged out, which could clear the locally-stored device name and other FxA-related customizations. For a dedicated Firefox device maybe it's not so simple.
I don't think I like this, it seems too easy to have device names that accidentally match, particularly if the device fills in a default based on its hardware platform etc. If we're going to do recovering devices, I think we should let the device provide some sort of fingerprint that it's extremely confident will be unique.