PageSourceSearch

https://transmundial.org/js/ss/frontend/CsrfTokenHandler.js?1790341883

js transmundial.org collected 2026-09-25 19:10:59 UTC 5,689 bytes, 158 lines download raw bytes

1/**
2 * Attaches the CSRF token (SS.csrfToken) to every POST the page sends.
3 *
4 * A page rendered without a session (SecurityComponent::deferToken()) ships
5 * SS.csrfToken empty and every `data[_Token][key]` input blank. The token is
6 * fetched from SS.csrfTokenUrl only when the visitor is about to need it, so a
7 * visitor who only reads pages never starts a session and their pages stay
8 * edge-cacheable. Fetching starts the session and sets its cookie; the value
9 * is written into SS.csrfToken and into the token inputs.
10 *
11 * The token is sha1(session id). It is fetched fresh at submit time -- not
12 * cached from page load -- so it matches the session the POST will run under
13 * even if the session was only just established.
14 */
15(function () {
16    if (typeof SS == 'undefined' || !SS.csrfTokenUrl) {
17        return;
18    }
19
20    // Deferred = the page shipped no token. When it shipped one (a session
21    // already existed at render), that token is live and this shim stays out
22    // of the way -- forms already carry a valid token.
23    if (SS.csrfToken) {
24        return;
25    }
26
27    var TOKEN_INPUT = 'input[name="data[_Token][key]"]';
28    var inFlight = null;
29    var readyForms = (typeof WeakSet !== 'undefined') ? new WeakSet() : null;
30
31    function fillInputs() {
32        if (!SS.csrfToken) {
33            return;
34        }
35        var inputs = document.querySelectorAll(TOKEN_INPUT);
36        for (var i = 0; i < inputs.length; i++) {
37            inputs[i].value = SS.csrfToken;
38        }
39    }
40
41    // Resolve to a token, fetching it (and starting the session) if needed.
42    // `forceFresh` re-fetches even when one is cached, so a submit always uses
43    // a token minted for the current session.
44    function getToken(forceFresh) {
45        if (SS.csrfToken && !forceFresh) {
46            return Promise.resolve(SS.csrfToken);
47        }
48        if (inFlight) {
49            return inFlight;
50        }
51        inFlight = new Promise(function (resolve) {
52            var xhr = new XMLHttpRequest();
53            xhr.open('GET', SS.csrfTokenUrl, true);
54            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
55            xhr.onreadystatechange = function () {
56                if (xhr.readyState !== 4) {
57                    return;
58                }
59                inFlight = null;
60                if (xhr.status === 200) {
61                    try {
62                        SS.csrfToken = JSON.parse(xhr.responseText).key || '';
63                    } catch (e) {}
64                }
65                fillInputs();
66                resolve(SS.csrfToken);
67            };
68            xhr.send();
69        });
70        return inFlight;
71    }
72
73    // Pre-warm on first interaction so the token is usually already fetched by
74    // the time a form is submitted. Passive, one-shot.
75    var prewarm = function () {
76        document.removeEventListener('pointerdown', prewarm, true);
77        document.removeEventListener('focusin', prewarm, true);
78        getToken(false);
79    };
80    document.addEventListener('pointerdown', prewarm, true);
81    document.addEventListener('focusin', prewarm, true);
82
83    // Capture phase, ahead of the form's own submit handlers (including
84    // FormHelper.js / Login.js which bind on 'submit' and read the inputs or
85    // SS.csrfToken then). Hold the submit, fetch a fresh token, fill the
86    // inputs, then let the same submit through once.
87    document.addEventListener('submit', function (event) {
88        var form = event.target;
89        if (!form || !form.querySelector || (form.method || '').toLowerCase() !== 'post') {
90            return;
91        }
92        // Engage for every POST form, even ones without a token input: some
93        // (the header login popup) build their AJAX body by hand and pick the
94        // token up from SS.csrfToken via the ajaxSend hook, so it must be
95        // fetched before the form's own submit handler runs.
96        if (readyForms && readyForms.has(form)) {
97            readyForms.delete(form);   // this is the re-dispatched submit; proceed
98            return;
99        }
100        event.preventDefault();
101        event.stopImmediatePropagation();
102        getToken(true).then(function () {
103            if (readyForms) {
104                readyForms.add(form);
105            }
106            if (form.requestSubmit) {
107                form.requestSubmit(event.submitter || undefined);
108            } else {
109                form.submit();
110            }
111        });
112    }, true);
113})();
114
115if (typeof Ext != 'undefined' && Ext.onReady) {
116    
117    Ext.require(['Ext.Ajax']);
118    
119    Ext.onReady(function(){
120
121        Ext.Ajax.on('beforerequest', function(conn, options, eOpts) {
122            if(!options.method || options.method.toUpperCase() == 'POST') {
123                if (!options.params) {
124                    options.params = {};
125                }
126                options.params['data[_Token][key]'] = SS.csrfToken;
127            }
128        });
129        
130    });
131}
132
133if (typeof jQuery != 'undefined') {
134
135    $( document ).ajaxSend(function(event, jqXHR, ajaxOptions) {
136
137        var s = ajaxOptions;
138        
139        if (s.type.toUpperCase() == 'POST') {
140
141            if (s.hasContent && s.contentType !== false) {
142                jqXHR.setRequestHeader( "Content-Type", s.contentType );
143            }
144            
145            if (!ajaxOptions.data) {
146                ajaxOptions.data = '';
147            }
148            
149            if($.type(ajaxOptions.data)=='object') {
150                ajaxOptions.data['data[_Token][key]'] = SS.csrfToken;
151            } else {
152                ajaxOptions.data += (ajaxOptions.data.length?'&':'') + 'data[_Token][key]=' + SS.csrfToken;
153            }
154        }
155        
156    });
157    
158}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.