Skip to content

Instantly share code, notes, and snippets.

@amcgregor
Last active February 12, 2023 06:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amcgregor/6ec7d5753f3fdffea16c50bbfd88cf92 to your computer and use it in GitHub Desktop.
Save amcgregor/6ec7d5753f3fdffea16c50bbfd88cf92 to your computer and use it in GitHub Desktop.
A collected sample model for user accounts, sessions, and supporting record types. (JSON sample needs updating to conform to the updated/public models.)
# Note: No __collection__ defined on most of these.
# They're for "embedding" (nesting) within a real collection-level document.
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
from marrow.mongo import Document, Field, Index
from marrow.mongo.trait import Queryabe, Identified
from marrow.mongo.field import Reference, Set, String, ObjectId, Date, Embed,
from .support import VerifiedMail, Address, Number, Contact
ARGON = PasswordHasher()
class Account(Queryable, Identified, Document):
__collection__ = 'accounts'
# Nested Structure Definitions
class Credential(Document):
# There are many subclasses of this to implement each type of authentication.
# Those specific implementations are isolated into their own modules.
...
def verify(self) -> bool:
"""Different implementations may have differing verification requirements (arguments)."""
...
class ArgonCredential(Document):
# https://argon2-cffi.readthedocs.io/en/stable/
class ArgonTransformer(Transformer):
def foreign(self, value, context):
return ARGON.hash(value)
hash = Field(transformer=ArgonTransformer)
def verify(self, password:str) -> bool:
try:
if not self.ARGON.verify(self.hash, password): return False
if self.ARGON.check_needs_rehash(self.hash):
self.hash = password
return True
except VerifyMismatchError:
return False
class Membership(Document):
"""Organization membership and roles."""
ROLE = {
'admin', # Full access to organization data.
'staff', # Standard "staff" back-office user.
'tech', # Technical access, but without access to operational data.
'reviewer', # Involved in review and approval processes.
'payee', # Sent a copy of invoices, otherwise handling payment processing and invoices.
'analyst', # Access to reports and analytics.
'writer', # A form of restricted staff user, e.g. "guest editor".
...
}
org = Reference('Organization')
roles = Set(kind=String(choices=ROLE, assign=True))
class Setting(Document):
"""An abstract key=value associated with this user's interactions with another database object.
This forms the user configuration layer, overriding inherited organization settings for the related objects.
String examples:
ent=Service("Twitter") k=oauth_token
ent=Service("Twitter") k=oauth_token_secret
ent=CVManagerJobSync() k=prefix
ent=CVManagerJobSync() k=qs
Slightly more involved example:
ent=KenexaJobSync() k=employee v=int
ent=KenexaJobSync() k=partner v=int
ent=KenexaJobSync() k=site v=URI
ent=KenexaJobSync() k=extra v=List[Field]
ent=KenexaJobSync() k=filter v=List[dict]
"""
entity = Reference() # The "thing" this setting is associated with.
key = String() # The name of the setting.
value = Field() # The value of that setting; a free-form field storing any rich type given.
class LastContact(Document):
when = Date()
by = Reference(default=None) # If None, system-automated contact was had.
# Field Definitions
# `id` inherited from Identified
username = String(default=None) # Optional "short name" for authentication.
locale = String(default='fr-CA-u-tz-cator-cu-CAD', assign=True) # IETF BCP-47 language tag.
name = String() # Required "full name".
contact = Embed(Contact, assign=True) # Asign ensures an empty instantiated Contact is populated.
email = Array(VerifiedMail, assign=True) # E-mail addresses.
numbers = Array(Number, assign=True) # Phone numbers.
credential = Array(Credential, project=False) # Multiple credentials are permitted.
membership = Array(Membership, assign=True) # Organization membership.
permission = Array(String(), assign=True) # Abstract permission tags.
settings = Array(Setting, assign=True) # See above.
notes = Array(Note, assign=True) # Arbitrary notes.
modified = Date(assign=True) # If assignment is requested and no default given, dates default to utcnow().
seen = Date() # Last time seen active on site.
contacted = Embed(LastContact, assign=True)
# Additional Indexes
_username = Index('username', unique=True, sparse=True)
_email_address = Index('email.address', unique=True)
# Authentication Methods
@classmethod
def lookup(cls, context, identifier) -> Optional['Account']:
# Look up a user object by ID, or return None if invalid.
...
@classmethod
def authenticate(cls, context, challenge, response) -> Optional['Account']:
# Return a user account object or None if authenticaiton failed.
...
"""Active session model.
Only create a session when first written to. Otherwise, there's absolutely no need to set a cookie.
"""
from marrow.mongo import Field
from marrow.mongo.field import String, ObjectId, Embed
from web.session.mongo import MongoSessionStorage
class Session(MongoSessionStorage):
__collection__ = 'sessions'
locale = String(default='en-CA-u-tz-cator') # Current localization values.
channel = String(default=None) # https://github.com/marrow/contentment/wiki/Deferred-Chunk-Generation#broker
# ## Authorization
account = ObjectId(default=None) # Currently logged in user.
sudoer = ObjectId(default=None) # The actual user, if the account is a masquerade.
oauth = Field(default=None) # Social integration authentication temporary state.
csrf = String(default=None) # CSRF challenge.
# ## Order Processing
order = Embed(default=None) # The Invoice currently being worked on, embedded.
"""Reusable support data models.
Generally utilized embedded within other root-level document collections.
"""
from uri import URI
from marrow.mongo import Document
from marrow.mongo.field import Array, Boolean, Date, String, Number
class VerifiedMail(Document):
__pk__ = 'address'
address = String(required=True) # The e-mail address itself.
added = Date(default=utcnow, write=False) # When was this address added?
verified = Date(default=None, write=False) # Has the user verified the address, and if so, when?
def send(self) -> bool:
...
def verify(self) -> bool:
...
def __uri__(self) -> URI:
return URI(f"mailto:{self.address}")
class Address(Document): # This has been... simplified.
address = String()
city = String()
region = String()
country = String()
postal = String()
def geocode(self) -> Tuple[float,float]:
...
class Number(Document):
__pk__ = 'number'
KINDS = {
'business',
'personal',
'mobile',
}
kind = String(choices=KINDS, default="business", assign=True)
number = String()
primary = Boolean(default=False)
def __url__(self):
return URI(f"tel:{self.number}")
class Contact(Document):
name = String()
title = String()
number = Array(Embed(Number), assign=True)
{
"_id": {"$oid": "5192822481c331a1432cc584"},
"username": "amcgregor",
"locale": "en-CA-u-tz-cator-cu-CAD",
"name": "Alice Bevan-McGregor",
"email": [
{"address": "amcgregor@", "verified": {"$date": 1475540746512}, "primary": true},
{"address": "alice@", "verified": {"$date": 1475540746512}},
{"address": "alice.mcgregor@", "verified": {"$date": 1475540746512}}
],
"numbers": [
{"kind": "work", "number": "555 555-5555", "primary": true},
{"kind": "mobile", "number": ""}
],
"credential": [
{"password": {"$binary": "", "$type": "00"}},
{"yubikey": [""]},
{"u2f": "", "key": {"$binary": "", "$type": "00"}, "counter": 0}
],
"membership": [
{"org": {"$oid": "51cb38c181c331cd4698eebc"}, "roles": ["admin"], "primary": true},
{"org": {"$oid": "52961d6658b70002d2af25a8"}, "roles": ["editor"]}
],
"permission": ["active", "admin"],
"favourite": {
"companies": [
{"$oid": "51cb38c181c331cd4698eebc"}
],
"jobs": [],
"sources": []
},
"settings": [
{
"source": {"$oid": "5419d80312497d4e57421e23"},
"access_token": "",
"display_name": "GothAlice",
"expires_at": ,
"token_type": "Bearer"
}
],
"notes": [
{
"id": {"$oid": "57f4598f927cc62ddce742e8"},
"author": {"id": {"$oid": "5192822481c331a1432cc584"}, "name": "Alice Bevan-McGregor"},
"comment": "First!"
}
],
"modified": {"$date": 1473776775604},
"seen": {"$date": 1475540746512}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment