Repository: jupyterhub/jupyterhub

Issue:
Errors running master on postgresql
Am trying to run stress tests with postgres, and am running into the following issue:

```
[W 2017-07-24 05:59:15.964 JupyterHub log:122] 400 POST /hub/api/oauth2/token (jupyterhub-benchmark-1-t9mv510@10.244.17.15) 19.94ms
ERROR:oauth2.application:Uncaught Exception
Traceback (most recent call last):
  File "/srv/venv/site-packages/oauth2/__init__.py", line 117, in dispatch
    return grant_type.process(request, response, environ)
  File "/srv/venv/site-packages/oauth2/grant.py", line 525, in process
    user_id=self.user_id)
  File "/srv/venv/site-packages/oauth2/grant.py", line 381, in create_token
    self.access_token_store.save_token(access_token)
  File "/srv/venv/site-packages/jupyterhub/oauth/store.py", line 76, in save_token
    user = self.db.query(orm.User).filter(orm.User.id == access_token.user_id).first()
  File "/srv/venv/site-packages/sqlalchemy/orm/query.py", line 2755, in first
    ret = list(self[0:1])
  File "/srv/venv/site-packages/sqlalchemy/orm/query.py", line 2547, in __getitem__
    return list(res)
  File "/srv/venv/site-packages/sqlalchemy/orm/query.py", line 2855, in __iter__
    return self._execute_and_instances(context)
  File "/srv/venv/site-packages/sqlalchemy/orm/query.py", line 2876, in _execute_and_instances
    close_with_result=True)
  File "/srv/venv/site-packages/sqlalchemy/orm/query.py", line 2885, in _get_bind_args
    **kw
  File "/srv/venv/site-packages/sqlalchemy/orm/query.py", line 2867, in _connection_from_session
    conn = self.session.connection(**kw)
  File "/srv/venv/site-packages/sqlalchemy/orm/session.py", line 998, in connection
    execution_options=execution_options)
  File "/srv/venv/site-packages/sqlalchemy/orm/session.py", line 1003, in _connection_for_bind
    engine, execution_options)
  File "/srv/venv/site-packages/sqlalchemy/orm/session.py", line 382, in _connection_for_bind
    self._assert_active()
  File "/srv/venv/site-packages/sqlalchemy/orm/session.py", line 276, in _assert_active
    % self._rollback_exception
sqlalchemy.exc.InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with 
this Session, first issue Session.rollback(). Original exception was: (psycopg2cffi._impl.exceptions.DataError) value too long for type character varying(64)
 [SQL: 'INSERT INTO oauth_access_tokens (client_id, grant_type, expires_at, refresh_token, refresh_expires_at, user_id, hashed, prefix) VALUES (%(client_id)s, %(grant_type)s, %(expires_at)s, %(refresh_token)s, %(refresh_expires_at)s, %(user_id)s, %(hashed)s, %(prefix)s) RETURNING oauth_access_tokens.id'] [parameters: {'client_id': 'user-jupyterhub-benchmark-1-t9mv50', 'grant_type': 'authorization_code', 'expires_at': None, 'refresh_token': None, 'refresh_expires_at': None, 'user_id': 103, 'hashed': 'sha512:16384:78ae42320afcc510:19765e6a0834fbf0825f6154bb0b8b338dd0b9d3a2590869344824afd6f4a196a3ef0e89619d24057bba2889225b1386fbff3219a069d4ecd2376ced99472332', 'prefix': '9479'}]
```

Seems to happen to all of the requests.


Discussion:
I guess 64 is too small for the oauth_token field?
I manually ran an ALTER TABLE and it worked.

Resolving patch:
diff --git a/jupyterhub/apihandlers/auth.py b/jupyterhub/apihandlers/auth.py
--- a/jupyterhub/apihandlers/auth.py
+++ b/jupyterhub/apihandlers/auth.py
@@ -26,6 +26,11 @@ def get(self, token):
             model = self.user_model(self.users[orm_token.user])
         elif orm_token.service:
             model = self.service_model(orm_token.service)
+        else:
+            self.log.warning("%s has no user or service. Deleting..." % orm_token)
+            self.db.delete(orm_token)
+            self.db.commit()
+            raise web.HTTPError(404)
         self.write(json.dumps(model))
 
     @gen.coroutine
diff --git a/jupyterhub/oauth/store.py b/jupyterhub/oauth/store.py
--- a/jupyterhub/oauth/store.py
+++ b/jupyterhub/oauth/store.py
@@ -74,7 +74,10 @@ def save_token(self, access_token):
         """
         
         user = self.db.query(orm.User).filter(orm.User.id == access_token.user_id).first()
+        if user is None:
+            raise ValueError("No user for access token: %s" % access_token.user_id)
         orm_access_token = orm.OAuthAccessToken(
+            generated=True,
             client_id=access_token.client_id,
             grant_type=access_token.grant_type,
             expires_at=access_token.expires_at,
diff --git a/jupyterhub/orm.py b/jupyterhub/orm.py
--- a/jupyterhub/orm.py
+++ b/jupyterhub/orm.py
@@ -407,14 +407,14 @@ class OAuthAccessToken(Hashed, Base):
     client_id = Column(Unicode(1023))
     grant_type = Column(Enum(GrantType), nullable=False)
     expires_at = Column(Integer)
-    refresh_token = Column(Unicode(64))
+    refresh_token = Column(Unicode(1023))
     refresh_expires_at = Column(Integer)
     user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'))
     user = relationship(User)
-    session = None # for API-equivalence with APIToken
+    service = None # for API-equivalence with APIToken
 
     # from Hashed
-    hashed = Column(Unicode(64))
+    hashed = Column(Unicode(1023))
     prefix = Column(Unicode(16), index=True)
     
     def __repr__(self):


Test patch: