We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Only show source codename on first session
# Feature request

## Description

A source is shown their codename on the `/lookup` page to help them remember it on their first session. This hint should not be shown again after the source logs out. The reason being is in the future, we maybe remove the actual codename from the cookie to prevent it from being used to login indefinitely (either by encrypting it or using some uuid that maps to the codename per session). If we show this on subsequent logins, an attack could steal the cookie, not learn the codename, but login see the codename, and then login again indefinitely.

Plus, is a source has logged in successfully, they definitely know their codename, and there is little (nothing?) to be gained by showing it again.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `securedrop/source_app/main.py`
Content:
```
1 import operator
2 import os
3 
4 from datetime import datetime
5 from flask import (Blueprint, render_template, flash, redirect, url_for, g,
6                    session, current_app, request, Markup, abort)
7 from flask_babel import gettext
8 from sqlalchemy.exc import IntegrityError
9 
10 import crypto_util
11 import store
12 
13 from db import Source, db_session, Submission, Reply, get_one_or_else
14 from rm import srm
15 from source_app.decorators import login_required
16 from source_app.utils import (logged_in, generate_unique_codename,
17                               async_genkey, normalize_timestamps,
18                               valid_codename)
19 from source_app.forms import LoginForm
20 
21 
22 def make_blueprint(config):
23     view = Blueprint('main', __name__)
24 
25     @view.route('/')
26     def index():
27         return render_template('index.html')
28 
29     @view.route('/generate', methods=('GET', 'POST'))
30     def generate():
31         if logged_in():
32             flash(gettext(
33                 "You were redirected because you are already logged in. "
34                 "If you want to create a new account, you should log out "
35                 "first."),
36                   "notification")
37             return redirect(url_for('.lookup'))
38 
39         codename = generate_unique_codename()
40         session['codename'] = codename
41         return render_template('generate.html', codename=codename)
42 
43     @view.route('/create', methods=['POST'])
44     def create():
45         filesystem_id = crypto_util.hash_codename(session['codename'])
46 
47         source = Source(filesystem_id, crypto_util.display_id())
48         db_session.add(source)
49         try:
50             db_session.commit()
51         except IntegrityError as e:
52             db_session.rollback()
53             current_app.logger.error(
54                 "Attempt to create a source with duplicate codename: %s" %
55                 (e,))
56         else:
57             os.mkdir(store.path(filesystem_id))
58 
59         session['logged_in'] = True
60         return redirect(url_for('.lookup'))
61 
62     @view.route('/lookup', methods=('GET',))
63     @login_required
64     def lookup():
65         replies = []
66         for reply in g.source.replies:
67             reply_path = store.path(g.filesystem_id, reply.filename)
68             try:
69                 reply.decrypted = crypto_util.decrypt(
70                     g.codename,
71                     open(reply_path).read()).decode('utf-8')
72             except UnicodeDecodeError:
73                 current_app.logger.error("Could not decode reply %s" %
74                                          reply.filename)
75             else:
76                 reply.date = datetime.utcfromtimestamp(
77                     os.stat(reply_path).st_mtime)
78                 replies.append(reply)
79 
80         # Sort the replies by date
81         replies.sort(key=operator.attrgetter('date'), reverse=True)
82 
83         # Generate a keypair to encrypt replies from the journalist
84         # Only do this if the journalist has flagged the source as one
85         # that they would like to reply to. (Issue #140.)
86         if not crypto_util.getkey(g.filesystem_id) and g.source.flagged:
87             async_genkey(g.filesystem_id, g.codename)
88 
89         return render_template(
90             'lookup.html',
91             codename=g.codename,
92             replies=replies,
93             flagged=g.source.flagged,
94             haskey=crypto_util.getkey(
95                 g.filesystem_id))
96 
97     @view.route('/submit', methods=('POST',))
98     @login_required
99     def submit():
100         msg = request.form['msg']
101         fh = request.files['fh']
102 
103         # Don't submit anything if it was an "empty" submission. #878
104         if not (msg or fh):
105             flash(gettext(
106                 "You must enter a message or choose a file to submit."),
107                   "error")
108             return redirect(url_for('main.lookup'))
109 
110         fnames = []
111         journalist_filename = g.source.journalist_filename
112         first_submission = g.source.interaction_count == 0
113 
114         if msg:
115             g.source.interaction_count += 1
116             fnames.append(
117                 store.save_message_submission(
118                     g.filesystem_id,
119                     g.source.interaction_count,
120                     journalist_filename,
121                     msg))
122         if fh:
123             g.source.interaction_count += 1
124             fnames.append(
125                 store.save_file_submission(
126                     g.filesystem_id,
127                     g.source.interaction_count,
128                     journalist_filename,
129                     fh.filename,
130                     fh.stream))
131 
132         if first_submission:
133             msg = render_template('first_submission_flashed_message.html')
134             flash(Markup(msg), "success")
135 
136         else:
137             if msg and not fh:
138                 html_contents = gettext('Thanks! We received your message.')
139             elif not msg and fh:
140                 html_contents = gettext('Thanks! We received your document.')
141             else:
142                 html_contents = gettext('Thanks! We received your message and '
143                                         'document.')
144 
145             msg = render_template('next_submission_flashed_message.html',
146                                   html_contents=html_contents)
147             flash(Markup(msg), "success")
148 
149         for fname in fnames:
150             submission = Submission(g.source, fname)
151             db_session.add(submission)
152 
153         if g.source.pending:
154             g.source.pending = False
155 
156             # Generate a keypair now, if there's enough entropy (issue #303)
157             entropy_avail = int(
158                 open('/proc/sys/kernel/random/entropy_avail').read())
159             if entropy_avail >= 2400:
160                 async_genkey(g.filesystem_id, g.codename)
161 
162         g.source.last_updated = datetime.utcnow()
163         db_session.commit()
164         normalize_timestamps(g.filesystem_id)
165 
166         return redirect(url_for('main.lookup'))
167 
168     @view.route('/delete', methods=('POST',))
169     @login_required
170     def delete():
171         query = Reply.query.filter(
172             Reply.filename == request.form['reply_filename'])
173         reply = get_one_or_else(query, current_app.logger, abort)
174         srm(store.path(g.filesystem_id, reply.filename))
175         db_session.delete(reply)
176         db_session.commit()
177 
178         flash(gettext("Reply deleted"), "notification")
179         return redirect(url_for('.lookup'))
180 
181     @view.route('/delete-all', methods=('POST',))
182     @login_required
183     def batch_delete():
184         replies = g.source.replies
185         if len(replies) == 0:
186             current_app.logger.error("Found no replies when at least one was "
187                                      "expected")
188             return redirect(url_for('.lookup'))
189 
190         for reply in replies:
191             srm(store.path(g.filesystem_id, reply.filename))
192             db_session.delete(reply)
193         db_session.commit()
194 
195         flash(gettext("All replies have been deleted"), "notification")
196         return redirect(url_for('.lookup'))
197 
198     @view.route('/login', methods=('GET', 'POST'))
199     def login():
200         form = LoginForm()
201         if form.validate_on_submit():
202             codename = request.form['codename'].strip()
203             if valid_codename(codename):
204                 session.update(codename=codename, logged_in=True)
205                 return redirect(url_for('.lookup', from_login='1'))
206             else:
207                 current_app.logger.info(
208                         "Login failed for invalid codename".format(codename))
209                 flash(gettext("Sorry, that is not a recognized codename."),
210                       "error")
211         return render_template('login.html', form=form)
212 
213     @view.route('/logout')
214     def logout():
215         if logged_in():
216             session.clear()
217             msg = render_template('logout_flashed_message.html')
218             flash(Markup(msg), "important hide-if-not-tor-browser")
219         return redirect(url_for('.index'))
220 
221     return view
222
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/securedrop/source_app/main.py b/securedrop/source_app/main.py
--- a/securedrop/source_app/main.py
+++ b/securedrop/source_app/main.py
@@ -38,6 +38,7 @@
 
         codename = generate_unique_codename()
         session['codename'] = codename
+        session['new_user'] = True
         return render_template('generate.html', codename=codename)
 
     @view.route('/create', methods=['POST'])
@@ -91,6 +92,7 @@
             codename=g.codename,
             replies=replies,
             flagged=g.source.flagged,
+            new_user=session.get('new_user', None),
             haskey=crypto_util.getkey(
                 g.filesystem_id))

{"golden_diff": "diff --git a/securedrop/source_app/main.py b/securedrop/source_app/main.py\n--- a/securedrop/source_app/main.py\n+++ b/securedrop/source_app/main.py\n@@ -38,6 +38,7 @@\n \n         codename = generate_unique_codename()\n         session['codename'] = codename\n+        session['new_user'] = True\n         return render_template('generate.html', codename=codename)\n \n     @view.route('/create', methods=['POST'])\n@@ -91,6 +92,7 @@\n             codename=g.codename,\n             replies=replies,\n             flagged=g.source.flagged,\n+            new_user=session.get('new_user', None),\n             haskey=crypto_util.getkey(\n                 g.filesystem_id))\n", "issue": "Only show source codename on first session\n# Feature request\r\n\r\n## Description\r\n\r\nA source is shown their codename on the `/lookup` page to help them remember it on their first session. This hint should not be shown again after the source logs out. The reason being is in the future, we maybe remove the actual codename from the cookie to prevent it from being used to login indefinitely (either by encrypting it or using some uuid that maps to the codename per session). If we show this on subsequent logins, an attack could steal the cookie, not learn the codename, but login see the codename, and then login again indefinitely.\r\n\r\nPlus, is a source has logged in successfully, they definitely know their codename, and there is little (nothing?) to be gained by showing it again.\n", "before_files": [{"content": "import operator\nimport os\n\nfrom datetime import datetime\nfrom flask import (Blueprint, render_template, flash, redirect, url_for, g,\n                   session, current_app, request, Markup, abort)\nfrom flask_babel import gettext\nfrom sqlalchemy.exc import IntegrityError\n\nimport crypto_util\nimport store\n\nfrom db import Source, db_session, Submission, Reply, get_one_or_else\nfrom rm import srm\nfrom source_app.decorators import login_required\nfrom source_app.utils import (logged_in, generate_unique_codename,\n                              async_genkey, normalize_timestamps,\n                              valid_codename)\nfrom source_app.forms import LoginForm\n\n\ndef make_blueprint(config):\n    view = Blueprint('main', __name__)\n\n    @view.route('/')\n    def index():\n        return render_template('index.html')\n\n    @view.route('/generate', methods=('GET', 'POST'))\n    def generate():\n        if logged_in():\n            flash(gettext(\n                \"You were redirected because you are already logged in. \"\n                \"If you want to create a new account, you should log out \"\n                \"first.\"),\n                  \"notification\")\n            return redirect(url_for('.lookup'))\n\n        codename = generate_unique_codename()\n        session['codename'] = codename\n        return render_template('generate.html', codename=codename)\n\n    @view.route('/create', methods=['POST'])\n    def create():\n        filesystem_id = crypto_util.hash_codename(session['codename'])\n\n        source = Source(filesystem_id, crypto_util.display_id())\n        db_session.add(source)\n        try:\n            db_session.commit()\n        except IntegrityError as e:\n            db_session.rollback()\n            current_app.logger.error(\n                \"Attempt to create a source with duplicate codename: %s\" %\n                (e,))\n        else:\n            os.mkdir(store.path(filesystem_id))\n\n        session['logged_in'] = True\n        return redirect(url_for('.lookup'))\n\n    @view.route('/lookup', methods=('GET',))\n    @login_required\n    def lookup():\n        replies = []\n        for reply in g.source.replies:\n            reply_path = store.path(g.filesystem_id, reply.filename)\n            try:\n                reply.decrypted = crypto_util.decrypt(\n                    g.codename,\n                    open(reply_path).read()).decode('utf-8')\n            except UnicodeDecodeError:\n                current_app.logger.error(\"Could not decode reply %s\" %\n                                         reply.filename)\n            else:\n                reply.date = datetime.utcfromtimestamp(\n                    os.stat(reply_path).st_mtime)\n                replies.append(reply)\n\n        # Sort the replies by date\n        replies.sort(key=operator.attrgetter('date'), reverse=True)\n\n        # Generate a keypair to encrypt replies from the journalist\n        # Only do this if the journalist has flagged the source as one\n        # that they would like to reply to. (Issue #140.)\n        if not crypto_util.getkey(g.filesystem_id) and g.source.flagged:\n            async_genkey(g.filesystem_id, g.codename)\n\n        return render_template(\n            'lookup.html',\n            codename=g.codename,\n            replies=replies,\n            flagged=g.source.flagged,\n            haskey=crypto_util.getkey(\n                g.filesystem_id))\n\n    @view.route('/submit', methods=('POST',))\n    @login_required\n    def submit():\n        msg = request.form['msg']\n        fh = request.files['fh']\n\n        # Don't submit anything if it was an \"empty\" submission. #878\n        if not (msg or fh):\n            flash(gettext(\n                \"You must enter a message or choose a file to submit.\"),\n                  \"error\")\n            return redirect(url_for('main.lookup'))\n\n        fnames = []\n        journalist_filename = g.source.journalist_filename\n        first_submission = g.source.interaction_count == 0\n\n        if msg:\n            g.source.interaction_count += 1\n            fnames.append(\n                store.save_message_submission(\n                    g.filesystem_id,\n                    g.source.interaction_count,\n                    journalist_filename,\n                    msg))\n        if fh:\n            g.source.interaction_count += 1\n            fnames.append(\n                store.save_file_submission(\n                    g.filesystem_id,\n                    g.source.interaction_count,\n                    journalist_filename,\n                    fh.filename,\n                    fh.stream))\n\n        if first_submission:\n            msg = render_template('first_submission_flashed_message.html')\n            flash(Markup(msg), \"success\")\n\n        else:\n            if msg and not fh:\n                html_contents = gettext('Thanks! We received your message.')\n            elif not msg and fh:\n                html_contents = gettext('Thanks! We received your document.')\n            else:\n                html_contents = gettext('Thanks! We received your message and '\n                                        'document.')\n\n            msg = render_template('next_submission_flashed_message.html',\n                                  html_contents=html_contents)\n            flash(Markup(msg), \"success\")\n\n        for fname in fnames:\n            submission = Submission(g.source, fname)\n            db_session.add(submission)\n\n        if g.source.pending:\n            g.source.pending = False\n\n            # Generate a keypair now, if there's enough entropy (issue #303)\n            entropy_avail = int(\n                open('/proc/sys/kernel/random/entropy_avail').read())\n            if entropy_avail >= 2400:\n                async_genkey(g.filesystem_id, g.codename)\n\n        g.source.last_updated = datetime.utcnow()\n        db_session.commit()\n        normalize_timestamps(g.filesystem_id)\n\n        return redirect(url_for('main.lookup'))\n\n    @view.route('/delete', methods=('POST',))\n    @login_required\n    def delete():\n        query = Reply.query.filter(\n            Reply.filename == request.form['reply_filename'])\n        reply = get_one_or_else(query, current_app.logger, abort)\n        srm(store.path(g.filesystem_id, reply.filename))\n        db_session.delete(reply)\n        db_session.commit()\n\n        flash(gettext(\"Reply deleted\"), \"notification\")\n        return redirect(url_for('.lookup'))\n\n    @view.route('/delete-all', methods=('POST',))\n    @login_required\n    def batch_delete():\n        replies = g.source.replies\n        if len(replies) == 0:\n            current_app.logger.error(\"Found no replies when at least one was \"\n                                     \"expected\")\n            return redirect(url_for('.lookup'))\n\n        for reply in replies:\n            srm(store.path(g.filesystem_id, reply.filename))\n            db_session.delete(reply)\n        db_session.commit()\n\n        flash(gettext(\"All replies have been deleted\"), \"notification\")\n        return redirect(url_for('.lookup'))\n\n    @view.route('/login', methods=('GET', 'POST'))\n    def login():\n        form = LoginForm()\n        if form.validate_on_submit():\n            codename = request.form['codename'].strip()\n            if valid_codename(codename):\n                session.update(codename=codename, logged_in=True)\n                return redirect(url_for('.lookup', from_login='1'))\n            else:\n                current_app.logger.info(\n                        \"Login failed for invalid codename\".format(codename))\n                flash(gettext(\"Sorry, that is not a recognized codename.\"),\n                      \"error\")\n        return render_template('login.html', form=form)\n\n    @view.route('/logout')\n    def logout():\n        if logged_in():\n            session.clear()\n            msg = render_template('logout_flashed_message.html')\n            flash(Markup(msg), \"important hide-if-not-tor-browser\")\n        return redirect(url_for('.index'))\n\n    return view\n", "path": "securedrop/source_app/main.py"}], "after_files": [{"content": "import operator\nimport os\n\nfrom datetime import datetime\nfrom flask import (Blueprint, render_template, flash, redirect, url_for, g,\n                   session, current_app, request, Markup, abort)\nfrom flask_babel import gettext\nfrom sqlalchemy.exc import IntegrityError\n\nimport crypto_util\nimport store\n\nfrom db import Source, db_session, Submission, Reply, get_one_or_else\nfrom rm import srm\nfrom source_app.decorators import login_required\nfrom source_app.utils import (logged_in, generate_unique_codename,\n                              async_genkey, normalize_timestamps,\n                              valid_codename)\nfrom source_app.forms import LoginForm\n\n\ndef make_blueprint(config):\n    view = Blueprint('main', __name__)\n\n    @view.route('/')\n    def index():\n        return render_template('index.html')\n\n    @view.route('/generate', methods=('GET', 'POST'))\n    def generate():\n        if logged_in():\n            flash(gettext(\n                \"You were redirected because you are already logged in. \"\n                \"If you want to create a new account, you should log out \"\n                \"first.\"),\n                  \"notification\")\n            return redirect(url_for('.lookup'))\n\n        codename = generate_unique_codename()\n        session['codename'] = codename\n        session['new_user'] = True\n        return render_template('generate.html', codename=codename)\n\n    @view.route('/create', methods=['POST'])\n    def create():\n        filesystem_id = crypto_util.hash_codename(session['codename'])\n\n        source = Source(filesystem_id, crypto_util.display_id())\n        db_session.add(source)\n        try:\n            db_session.commit()\n        except IntegrityError as e:\n            db_session.rollback()\n            current_app.logger.error(\n                \"Attempt to create a source with duplicate codename: %s\" %\n                (e,))\n        else:\n            os.mkdir(store.path(filesystem_id))\n\n        session['logged_in'] = True\n        return redirect(url_for('.lookup'))\n\n    @view.route('/lookup', methods=('GET',))\n    @login_required\n    def lookup():\n        replies = []\n        for reply in g.source.replies:\n            reply_path = store.path(g.filesystem_id, reply.filename)\n            try:\n                reply.decrypted = crypto_util.decrypt(\n                    g.codename,\n                    open(reply_path).read()).decode('utf-8')\n            except UnicodeDecodeError:\n                current_app.logger.error(\"Could not decode reply %s\" %\n                                         reply.filename)\n            else:\n                reply.date = datetime.utcfromtimestamp(\n                    os.stat(reply_path).st_mtime)\n                replies.append(reply)\n\n        # Sort the replies by date\n        replies.sort(key=operator.attrgetter('date'), reverse=True)\n\n        # Generate a keypair to encrypt replies from the journalist\n        # Only do this if the journalist has flagged the source as one\n        # that they would like to reply to. (Issue #140.)\n        if not crypto_util.getkey(g.filesystem_id) and g.source.flagged:\n            async_genkey(g.filesystem_id, g.codename)\n\n        return render_template(\n            'lookup.html',\n            codename=g.codename,\n            replies=replies,\n            flagged=g.source.flagged,\n            new_user=session.get('new_user', None),\n            haskey=crypto_util.getkey(\n                g.filesystem_id))\n\n    @view.route('/submit', methods=('POST',))\n    @login_required\n    def submit():\n        msg = request.form['msg']\n        fh = request.files['fh']\n\n        # Don't submit anything if it was an \"empty\" submission. #878\n        if not (msg or fh):\n            flash(gettext(\n                \"You must enter a message or choose a file to submit.\"),\n                  \"error\")\n            return redirect(url_for('main.lookup'))\n\n        fnames = []\n        journalist_filename = g.source.journalist_filename\n        first_submission = g.source.interaction_count == 0\n\n        if msg:\n            g.source.interaction_count += 1\n            fnames.append(\n                store.save_message_submission(\n                    g.filesystem_id,\n                    g.source.interaction_count,\n                    journalist_filename,\n                    msg))\n        if fh:\n            g.source.interaction_count += 1\n            fnames.append(\n                store.save_file_submission(\n                    g.filesystem_id,\n                    g.source.interaction_count,\n                    journalist_filename,\n                    fh.filename,\n                    fh.stream))\n\n        if first_submission:\n            msg = render_template('first_submission_flashed_message.html')\n            flash(Markup(msg), \"success\")\n\n        else:\n            if msg and not fh:\n                html_contents = gettext('Thanks! We received your message.')\n            elif not msg and fh:\n                html_contents = gettext('Thanks! We received your document.')\n            else:\n                html_contents = gettext('Thanks! We received your message and '\n                                        'document.')\n\n            msg = render_template('next_submission_flashed_message.html',\n                                  html_contents=html_contents)\n            flash(Markup(msg), \"success\")\n\n        for fname in fnames:\n            submission = Submission(g.source, fname)\n            db_session.add(submission)\n\n        if g.source.pending:\n            g.source.pending = False\n\n            # Generate a keypair now, if there's enough entropy (issue #303)\n            entropy_avail = int(\n                open('/proc/sys/kernel/random/entropy_avail').read())\n            if entropy_avail >= 2400:\n                async_genkey(g.filesystem_id, g.codename)\n\n        g.source.last_updated = datetime.utcnow()\n        db_session.commit()\n        normalize_timestamps(g.filesystem_id)\n\n        return redirect(url_for('main.lookup'))\n\n    @view.route('/delete', methods=('POST',))\n    @login_required\n    def delete():\n        query = Reply.query.filter(\n            Reply.filename == request.form['reply_filename'])\n        reply = get_one_or_else(query, current_app.logger, abort)\n        srm(store.path(g.filesystem_id, reply.filename))\n        db_session.delete(reply)\n        db_session.commit()\n\n        flash(gettext(\"Reply deleted\"), \"notification\")\n        return redirect(url_for('.lookup'))\n\n    @view.route('/delete-all', methods=('POST',))\n    @login_required\n    def batch_delete():\n        replies = g.source.replies\n        if len(replies) == 0:\n            current_app.logger.error(\"Found no replies when at least one was \"\n                                     \"expected\")\n            return redirect(url_for('.lookup'))\n\n        for reply in replies:\n            srm(store.path(g.filesystem_id, reply.filename))\n            db_session.delete(reply)\n        db_session.commit()\n\n        flash(gettext(\"All replies have been deleted\"), \"notification\")\n        return redirect(url_for('.lookup'))\n\n    @view.route('/login', methods=('GET', 'POST'))\n    def login():\n        form = LoginForm()\n        if form.validate_on_submit():\n            codename = request.form['codename'].strip()\n            if valid_codename(codename):\n                session.update(codename=codename, logged_in=True)\n                return redirect(url_for('.lookup', from_login='1'))\n            else:\n                current_app.logger.info(\n                        \"Login failed for invalid codename\".format(codename))\n                flash(gettext(\"Sorry, that is not a recognized codename.\"),\n                      \"error\")\n        return render_template('login.html', form=form)\n\n    @view.route('/logout')\n    def logout():\n        if logged_in():\n            session.clear()\n            msg = render_template('logout_flashed_message.html')\n            flash(Markup(msg), \"important hide-if-not-tor-browser\")\n        return redirect(url_for('.index'))\n\n    return view\n", "path": "securedrop/source_app/main.py"}]}